<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>Hacker News Personal Blogs 2024 | All Blog Posts</title><link>https://hn-blogs.kronis.dev/feed.xml</link><description>A collection of blog posts from users of Hacker News, based on RSS feeds.</description><language>en-US</language><lastBuildDate>Wed, 20 May 2026 00:23:25 GMT</lastBuildDate><generator>rfeed v1.1.1</generator><docs>https://github.com/svpino/rfeed/blob/master/README.md</docs><item><title>What Even Was 2024?</title><link>https://seankilleen.com/2024/12/what-even-was-2024/</link><description>Ed. Note: This is a personal post. Comments aren’t on, for a reason.</description><author>SeanKilleen.com</author><pubDate>Tue, 31 Dec 2024 20:00:00 GMT</pubDate><guid isPermaLink="true">https://seankilleen.com/2024/12/what-even-was-2024/</guid></item><item><title>Year in Review: 2024</title><link>https://www.danielcorin.com/posts/2024/2024-year-in-review/</link><description>Year in Review: 2024</description><author>Thought Eddies</author><pubDate>Tue, 31 Dec 2024 17:50:04 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2024/2024-year-in-review/</guid></item><item><title>My Default Apps at the End of 2024</title><link>https://srijan.ch/my-default-apps-at-the-end-of-2024</link><description>I saw a few blog posts with people sharing their default apps for the year, and I wanted to share mine as well. Here's the list: 📨 Mail Service: Fastmail 📮 Mail Client: Fastmail web, mu4e (Emacs), FairEmail (Android) 📝 Notes: Markdown and Org files in denote (Emacs), Markor (Android) ✅ To-Do: GTD using Orgmode (Emacs), Orgzly Revived (Android) 📆 Calendar: Google Calendar 🙍🏻‍♂️ Contacts: Google …</description><author>Srijan Choudhary, all posts</author><pubDate>Tue, 31 Dec 2024 17:10:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/my-default-apps-at-the-end-of-2024</guid></item><item><title>Virtue Ethics for Animal Rights Activists - A Practical Approach to Becoming a Better Activist</title><link>https://joshbaldwin.substack.com/p/virtue-ethics-for-animal-rights-activists</link><description>I previously posted about identity being key to self-improvement. However, the identity of &amp;#8220;animal rights activist&amp;#8221; is broad. Having the identity of being an animal rights activist is easier said than done. Using virtues, we can break down identity into distinct features that are easier to understand and implement.</description><author>Josh Baldwin</author><pubDate>Tue, 31 Dec 2024 17:00:45 GMT</pubDate><guid isPermaLink="true">https://joshbaldwin.substack.com/p/virtue-ethics-for-animal-rights-activists</guid></item><item><title>Best of 2024</title><link>https://siddhesh.substack.com/p/best-of-2024</link><description>All my favourite things from this year</description><author>Obvious Bicycle</author><pubDate>Tue, 31 Dec 2024 14:25:16 GMT</pubDate><guid isPermaLink="true">https://siddhesh.substack.com/p/best-of-2024</guid></item><item><title>Looking back at 2024</title><link>/2024/</link><description>&lt;p&gt;I’m determined to make a tradition of this, which is why I find myself in an empty office on New Years Eve trying to remember what’s gone by these last twelve months.&lt;/p&gt;

&lt;p&gt;I ended &lt;a href="https://blog.lawrencejones.dev/2023/"&gt;“Looking back at 2023”&lt;/a&gt; saying “brace for impact” which I had thought meant I was prepared for this year, but in retrospect I wasn’t even close. It’s a common story in start-ups to say “thank god that’s over, next year will be easier!” and be repeatedly wrong, but 2024 was particularly hard.&lt;/p&gt;

&lt;p&gt;There is a lot to show for it, though.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="#the-work"&gt;The work&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href="#releasing-on-call-january-march"&gt;Releasing On-call (January-March)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#building-on-our-release-april-june"&gt;Building on the release (April-June)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#scaling-to-larger-customers-july-september"&gt;Scaling to larger customers (July-September)&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#ai-forever"&gt;AI (forever)&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;a href="#everything-else"&gt;Everything else&lt;/a&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;a href="#ups-and-downs"&gt;Ups and downs&lt;/a&gt;&lt;/li&gt;
      &lt;li&gt;&lt;a href="#talks-and-conferences"&gt;Talks and conferences&lt;/a&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="the-work"&gt;The work&lt;/h2&gt;

&lt;p&gt;This year was about three things: releasing and then extending On-call, conferences and external work, then AI.&lt;/p&gt;

&lt;h3 id="releasing-on-call-january-march"&gt;Releasing On-call (January-March)&lt;/h3&gt;

&lt;p&gt;I started 2024 hell bent on getting our on-call product released, having been working on it since July the previous year.&lt;/p&gt;

&lt;p&gt;In my previous recap I’d described On-call as “our most ambitious product yet” with one part being the reliability demands that come with a paging system. I was once a Principal SRE running large payment systems and I needed to become that person again as we ramped up for release.&lt;/p&gt;

&lt;p&gt;This meant:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Building a plan to ensure On-call would be rock-solid&lt;/li&gt;
  &lt;li&gt;Reviewing every piece of code, adding tests, confirming edge cases&lt;/li&gt;
  &lt;li&gt;Running production drills to properly test everything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve got a draft blog post I’m yet to publish called “10 days of load testing and drills” which is all about that last point:&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Load testing interface showing drill scenarios and test results" src="/assets/images/2024-load-testing-drills.png" /&gt;
&lt;/figure&gt;

&lt;p&gt;To summarise: I spent several weeks in February working with Martha (an engineer from the team) to kick the living crap out of On-call before we released it.&lt;/p&gt;

&lt;p&gt;By a fair margin, these weeks were the most fun I had this year. We had to build out core observability tooling (we had just setup Grafana before the break, but had no dashboards, docs or best-practices) and plan a load testing strategy, then run drills with each engineer in the team trying to repeatedly break the system.&lt;/p&gt;

&lt;p&gt;Reliability and system design is my wheelhouse and I was bound to find it fun, but the most rewarding part was working with Martha. She joined the project with almost zero o11y experience and left being one of the most competent at the company, having listened to me drone on about the “importance of dashboard design” and what must’ve seemed like a load of other nonsense.&lt;/p&gt;

&lt;p&gt;Working with Martha and each of the team to take proper ownership over On-call was extremely rewarding. It’s a testament to this work that since launch, On-call has exceeded 99.99% availability, even having onboarded hundreds of customers, some pretty large.&lt;/p&gt;

&lt;p&gt;We have a &lt;a href="https://incident.io/hubs/building-on-call"&gt;“Behind-the-scenes building On-call”&lt;/a&gt; blog series that I recommend you check-out, but you can see how well the team met this challenge through their writing:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Martha with &lt;a href="https://incident.io/hubs/building-on-call/building-on-call-our-observability-strategy"&gt;“Our observability strategy”&lt;/a&gt; which should be required reading for any team rolling out o11y&lt;/li&gt;
  &lt;li&gt;Rory Malcolm on &lt;a href="https://incident.io/hubs/building-on-call/continually-testing-our-product-with-smoke-tests"&gt;“Continually testing with smoke tests”&lt;/a&gt; talking about the automated tests we run continuously to ensure we never miss a page&lt;/li&gt;
  &lt;li&gt;Leo Sjöberg on building on the horrible &lt;a href="https://incident.io/hubs/building-on-call/the-complexity-of-phone-networks"&gt;“Complexity of phone networks”&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ask anyone: four nines availability is no joke. There are teams in FANG who will tell you this isn’t possible for company like ours to achieve that, especially for a system like this.&lt;/p&gt;

&lt;p&gt;Well, this team proves that wrong.&lt;/p&gt;

&lt;h2 id="building-on-our-release-april-june"&gt;Building on our release (April-June)&lt;/h2&gt;

&lt;p&gt;We &lt;a href="https://incident.io/hubs/on-call/announcing-on-call"&gt;released On-call on March 5th&lt;/a&gt; but it’s easy to release a product, even one with a lot of fanfare, and find no users.&lt;/p&gt;

&lt;p&gt;Thankfully that did not happen with On-call, and to give a sense of what’s happened in the 9 months since we launched:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Over 60% of our customers have bought On-call&lt;/li&gt;
  &lt;li&gt;On-call alone is about as big as incident.io was 2.5 years into operation&lt;/li&gt;
  &lt;li&gt;We have sent 4M on-call notifications (app notifications, phone calls, SMS, etc)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s now, but the first few months after launching were essential! We might’ve just launched an On-call product but (quite entertainingly) so had FireHydrant, and Rootly had soft launched their On-call website (I think in response to our impending launch?) so we were in a crowded space.&lt;/p&gt;

&lt;p&gt;That meant several months of dialling into the customer feedback firehose and building whatever was needed to unblock deals, smoothing rough edges found during migration, and generally getting comfortable running this system.&lt;/p&gt;

&lt;p&gt;Probably the most noticeable of this work was &lt;a href="https://incident.io/changelog/smart-escalation-paths-for-improved-incident-handling"&gt;“Smart Escalation Paths”&lt;/a&gt; which introduced dynamic branching into escalations, supporting out-of-hours low urgency pages and a series of other improvements.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Smart Escalation Paths interface showing branching notification flows" src="/assets/images/2024-smart-escalation.png" /&gt;
&lt;/figure&gt;

&lt;p&gt;After so long building a product without customers (other than early partners) it was energising to plug back into the ship, feedback, iterate cycle. Honestly just seeing people go “wow, this is so good!” felt rewarding and helped the team feel their achievement.&lt;/p&gt;

&lt;p&gt;I also got to dive into mobile app development, something I’d never done before. This involved setting up authentication for the mobile app, a few small features, and eventually giving me the opportunity to build a silly diagram of the custom in-house over-the-air deployment flow we’d built!&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Diagram showing the over-the-air deployment flow for mobile apps" src="/assets/images/2024-deployment-flow.png" /&gt;
&lt;/figure&gt;

&lt;p&gt;Overall, not a bad quarter!&lt;/p&gt;

&lt;h2 id="scaling-to-larger-customers-july-september"&gt;Scaling to larger customers (July-September)&lt;/h2&gt;

&lt;p&gt;Thanks to our focus on “human on-call” our On-call product was getting a load of traction with small to medium companies. Crazy huh, imagine making it easy to request cover from your team, or connecting to HR systems and showing holidays alongside your schedules?&lt;/p&gt;

&lt;p&gt;But larger companies have more complex setups, and after assigning engineers to On-call sales calls, we found a bunch of small friction-points that made using us at scale difficult.&lt;/p&gt;

&lt;p&gt;Luckily the issue wasn’t “can incident.io work for my company” and in fact we are (in my opinion) the most flexible and powerful of the on-call products out there, largely due to our Catalog, meaning you can build whatever routing structure you like. Instead, it was “can I figure out how to make it work” which is a very different type of problem.&lt;/p&gt;

&lt;p&gt;On-call in particular is hard for this, as each customer has a unique set of:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Organisation structure&lt;/li&gt;
  &lt;li&gt;Observability/alerting tools&lt;/li&gt;
  &lt;li&gt;Alert routing and ownership mechanisms&lt;/li&gt;
  &lt;li&gt;Alerting philosophies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You have to solve the problem by fully understanding both across all customers and generically for our platform, and then deep into whatever slice of tools a customer may want to use. Finding solutions that are idiomatic wrt Grafana/Datadog/whatever you may be using requires an expertise in those tools too, in addition with a strong understanding of our system.&lt;/p&gt;

&lt;p&gt;We approached the problem by roasting our product, repeatedly setting up accounts and tracking how customers did it, forcing people to develop a ‘healthy impatience’ with our product so they’d balk at the first sign of friction.&lt;/p&gt;

&lt;p&gt;Eventually, we rolled out a series of changes that massively helped bigger companies like Intercom access the power of our product more easily.&lt;/p&gt;

&lt;p&gt;These ranged from:&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Enhanced configuration interface showing alert routing and team setup" src="/assets/images/2024-config-interface.png" /&gt;
&lt;/figure&gt;

&lt;ul&gt;
  &lt;li&gt;Redesigning alert config so we visually show how alerts flow from source to routes&lt;/li&gt;
  &lt;li&gt;Using AI to automatically parse alert attributes into Catalog fields, so we wire-up Team, Service, Feature or Product automatically&lt;/li&gt;
  &lt;li&gt;Adding onboarding sign-posts that help customers find their way to the right configuration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part was, after shipping any of these changes, sitting on a call with a customer helping them setup their config and watching as they skipped through the stages, or in some cases had everything automatically wire itself up for them.&lt;/p&gt;

&lt;p&gt;I regularly review our larger customer’s configurations and now, after all this, you can visually see how much cleaner their config has become with the product helping them improve it.&lt;/p&gt;

&lt;p&gt;Really rewarding, and one of the reasons why we’re closing this year having landed some of our biggest On-call sales yet.&lt;/p&gt;

&lt;h2 id="ai-forever"&gt;AI (forever)&lt;/h2&gt;

&lt;p&gt;If you’d asked me in January what my focus would be this year, AI wouldn’t have even made the list. And yet here I am, having spent the last quarter hitting my head against LLMs and building a foundation for what could be our most exciting product yet.&lt;/p&gt;

&lt;p&gt;Quite clearly AI changes the scene: auto-generating incident summaries is by far the most widely adopted of the features we built last year, and often the one people quote as the most useful. But there’s so much more that is possible with modern AI tools that can totally change incident response.&lt;/p&gt;

&lt;p&gt;That’s why I was asked to help setup a team whose focus would be ambitious, step-changes we could make to the product with AI. Initially I was helping build abstractions, which meant: building eval frameworks that could reliably test prompt behaviour, creating debugging tools that could introspect multi-step AI interactions, and developing architectural patterns like our map-reduce shortlister that efficiently searches data using AI models.&lt;/p&gt;

&lt;p&gt;But as we started pulling on the thread of AI we began seeing ever more opportunities to totally overhaul our product. It seemed increasingly obvious that someone was going to reimagine incident response with these tools, and we’d love that person to be… well, us.&lt;/p&gt;

&lt;p&gt;So I ended up on the team full time, which meant turning my job into a full time struggle.&lt;/p&gt;

&lt;p&gt;That’s because AI engineering is different. Not only are the tools themselves non-deterministic, but figuring out how to use them effectively is a process with very non-linear progress.&lt;/p&gt;

&lt;p&gt;Some weeks you’ll make breakthrough after breakthrough, and others you’ll spend days wrestling with prompt engineering only to end up exactly where you started. It’s a humbling experience, especially for engineers used to deterministic systems. Even – and in some cases especially – our most experienced and productive engineers hated it, and we had to build some resilience to that.&lt;/p&gt;

&lt;p&gt;We now have a roadmap, though, and unlike On-call we’re talking more publicly about what we’re building. We even have a beautiful homepage at &lt;a href="https://incident.io/ai"&gt;incident.io/ai&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;That roadmap breaks into three initiatives:&lt;/p&gt;

&lt;h3 id="scribe-launched"&gt;Scribe (launched)&lt;/h3&gt;

&lt;figure&gt;
  &lt;img alt="Scribe interface showing AI-generated meeting notes and action items" src="/assets/images/2024-scribe-interface.png" /&gt;
&lt;/figure&gt;

&lt;p&gt;Scribe joins incident calls for you – Zoom, GMeet, whatever – and takes notes, then processes them into key moments and actions that get relayed back to the incident channel.&lt;/p&gt;

&lt;p&gt;This is so obviously a perfect use case for AI, with it freeing capacity for humans to do the real work (responding to the incident) while helping them do their job better, by communicating their work more effectively to their colleagues.&lt;/p&gt;

&lt;p&gt;While I didn’t work on Scribe myself, it’s built on the AI primitives I’d helped build back in October such as our new prompt framework and eval test suite. It was a good example of how effective those abstractions have been, as it allowed the team to build the AI parts of Scribe very quickly, and even people who hadn’t worked with AI before felt confident in the outcome.&lt;/p&gt;

&lt;h3 id="chat-early-access"&gt;Chat (early access)&lt;/h3&gt;

&lt;figure&gt;
  &lt;img alt="Chat interface demonstrating natural language interaction with incident.io" src="/assets/images/2024-chat-interface.png" /&gt;
&lt;/figure&gt;

&lt;p&gt;The incident.io team no longer use slash commands to run their incidents. Instead, we chat to our bot and just tell it what we need:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“@incident.io draft me an update including why Postgres is checkpointing so regularly”&lt;/li&gt;
  &lt;li&gt;“@incident.io decline this incident and create a follow-up”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I am biased, but this is the best bot I’m yet to interact with, and people are finding novel use cases for it on the daily.&lt;/p&gt;

&lt;p&gt;We have a #copilot-pulse channel that pings every time someone interacts with the bot, along with a grade we assign to each interaction that we monitor for quality.&lt;/p&gt;

&lt;p&gt;It already does a huge range of things, but some of my favourite interactions are people just asking it for help with the incident. “Fix my query” or “what does that actually mean” where it saves them loads of time to get an answer immediately, one that has all the context of the incident.&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="Examples of AI copilot helping with incident management tasks" src="/assets/images/2024-copilot-examples.png" /&gt;
&lt;/figure&gt;

&lt;h3 id="investigation-coming-soon"&gt;Investigation (coming soon)&lt;/h3&gt;

&lt;figure&gt;
  &lt;img alt="Preview of the Investigation system showing automated incident analysis" src="/assets/images/2024-investigation-preview.png" /&gt;
&lt;/figure&gt;

&lt;p&gt;But the really exciting (and challenging) work is in our Investigations system. We’re pushing the boundaries of what’s practical with current AI technology, aiming to have the system automatically check dashboards, read logs, and build a coherent picture of what’s happening before responders even arrive. The goal is ambitious: turn hours-long debugging sessions into minutes by having AI do the heavy lifting.&lt;/p&gt;

&lt;p&gt;Personally, this thing has been a challenge. I struggle to sleep when I have an unsolved problem and I’ve gone weeks having no idea how to make this stuff work, where I’ve been dreaming every night of LLM prompts and all sorts. It’s been quite a nightmarish struggle, honestly.&lt;/p&gt;

&lt;p&gt;But when it works – when you see the bot correctly identify the root cause of an incident and suggest the exact right action – it feels like we’re glimpsing the future of incident response.&lt;/p&gt;

&lt;p&gt;This is definitely turning into “the thing” for 2025. The foundational work we’ve done this year, while often invisible, has set us up to deliver some pretty incredible capabilities to our customers. It’s not just about making existing workflows slightly better – it’s about fundamentally transforming how teams handle incidents.&lt;/p&gt;

&lt;h2 id="everything-else"&gt;Everything else&lt;/h2&gt;

&lt;p&gt;I’m getting dangerously close to needing to leave for NYE, so I’ll keep this short.&lt;/p&gt;

&lt;h3 id="ups-and-downs"&gt;Ups and downs&lt;/h3&gt;

&lt;p&gt;Looking at what we’ve achieved in 2024 is incredible. Even just the success of On-call is massive, ignoring everything else we’ve done.&lt;/p&gt;

&lt;p&gt;It’s been really tough, though. We’re in great shape now but things weren’t as rosy at the start of the year, leading to some hard decisions and parting with some of the team in June.&lt;/p&gt;

&lt;p&gt;I’d never been close to layoffs before and definitely not in a team as close as ours. This sucked for everyone, a totally shitty experience that leaves some nasty scars for everyone it touches, even when handled well (which, in my opinion, it was).&lt;/p&gt;

&lt;p&gt;At this point it’s been 3.5 years since I joined incident, having given myself two days (otherwise known as a weekend) between a 6 year stint at GoCardless that was similarly intense. While I’m focused on the next few months, I want to carve out a proper break for myself next year to reset a bit, as it’s been all consuming for a while now.&lt;/p&gt;

&lt;p&gt;Just start-ups, I guess!&lt;/p&gt;

&lt;h3 id="talks-and-conferences"&gt;Talks and conferences&lt;/h3&gt;

&lt;figure&gt;
  &lt;img alt="Collection of presentation slides from various conference talks" src="/assets/images/2024-conference-slides.png" /&gt;
&lt;/figure&gt;

&lt;p&gt;On a positive note I spoke a lot more this year, and travelled a bunch to do it. Some highlights were:&lt;/p&gt;

&lt;h4 id="architecture-at-incidentio-netflix"&gt;Architecture at incident.io (Netflix)&lt;/h4&gt;

&lt;p&gt;I have a huge amount of respect for Netflix engineering which has only been strengthened by my experience working with them as a customer.&lt;/p&gt;

&lt;p&gt;It was a big moment for me when I was invited to give a presentation on incident.io’s architecture in our of the internal engineering technical deep-dives.&lt;/p&gt;

&lt;p&gt;Over one hundred engineers came to listen to how we built our little incident app, with subjects ranging from Postgres TOAST tables to load testing and modular monoliths. Getting grilled by Principal engineers at one of &lt;em&gt;the&lt;/em&gt; infrastructure giants was a massive highlight for me!&lt;/p&gt;

&lt;h4 id="starting-from-nothing-staffplus-new-york"&gt;Starting from nothing, StaffPlus, New York&lt;/h4&gt;

&lt;p&gt;LeadDev and StaffPlus are really special conferences to me, having attended them and listened closely in my early career and having always appreciated their work.&lt;/p&gt;

&lt;p&gt;New York is a pretty insane flagship location to get invited to talk, but even cooler if you’re talking about how to “start from nothing” with big company initiatives, and pulling the covers back on the experience of a Staff individual contributor in those situations.&lt;/p&gt;

&lt;p&gt;I’d also never been to New York, and got to visit a bunch of friends from our office while there.&lt;/p&gt;

&lt;p&gt;You can watch the recording here: &lt;a href="https://www.youtube.com/watch?v=Ui5e7-3i-ZQ"&gt;“Starting from nothing”&lt;/a&gt;&lt;/p&gt;

&lt;h4 id="incidents-are-a-whole-organization-game-sev0-san-francisco"&gt;Incidents are a whole organization game, Sev0, San Francisco&lt;/h4&gt;

&lt;p&gt;We ran our own conference this year called “Sev0” which was &lt;strong&gt;so much fun&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Firstly, if you’re a company like us with a bunch of top tech logos, SF is where you go to meet your customers. Having dinner with the speakers beforehand was quietly mindblowing for me, when you get an insight into these tech behemoths and how they do things through some of their most senior staff.&lt;/p&gt;

&lt;p&gt;I delivered a talk on leveraging organisational context during incidents, a topic particularly close to my heart after fighting tooth and nail to build our Catalog product as a fully flexible schema’d store, all to power use cases like syncing your CRM.&lt;/p&gt;

&lt;p&gt;The venue was amazing, the people totally awesome, 10/10 would recommend.&lt;/p&gt;

&lt;p&gt;You can see the talk and transcript at &lt;a href="https://www.sev0.com/sessions/organization-aware-incident-response"&gt;“Organization-aware incident response”&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="wrapping-up"&gt;Wrapping up&lt;/h2&gt;

&lt;p&gt;Looking back, this has been a year of massive change – both planned and unexpected.&lt;/p&gt;

&lt;p&gt;We started with the intensity of launching On-call, a product that exceeded even our ambitious reliability goals and has grown to be about as big as incident.io was at 2.5 years. Then came the harder moments in summer that tested our resilience as a team, and eventually dropping myself headfirst into a deep sea of AI.&lt;/p&gt;

&lt;p&gt;It’s been really hard, even harder than the previous years and what I’d expected it to be!&lt;/p&gt;

&lt;p&gt;Christmas has been a time of reflection for me and I’ve processed a lot, leaving me feeling like I fit a whole load of life experience into the last year.&lt;/p&gt;

&lt;p&gt;I’m again reminding myself to be appreciative of the journey, and that the experience I’m having now will be the one I miss in a couple of years, and not to waste any moment of it.&lt;/p&gt;

&lt;p&gt;So not making the same mistake again: next year is going to be harder again. I will break myself several times over before fully cracking these AI products, I will repeatedly feel hopeless and like I’m not able to meet the challenge, and there will be curveballs I don’t expect which totally throw me.&lt;/p&gt;

&lt;p&gt;If we’ve achieved as much at the end of 2025 as we did this year, though, it’ll have been worth it.&lt;/p&gt;</description><author>Lawrence Jones</author><pubDate>Tue, 31 Dec 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">/2024/</guid></item><item><title>2024 in Review - 100 Books</title><link>https://lagomor.ph/2024/12/2024-in-review-100-books/</link><description>&lt;p&gt;I read 100 books this year as a challenge to myself. The hardest part about reading 100 books in a year is fomatting them cleanly afterward on your website. The second hardest part is tracking them all. The actual activity is a cinch.&lt;/p&gt;
&lt;p&gt;Reading 100 books this year was directly inspired from stories of Theodore Roosevelt, who read one book a day, often multiple. The thing I&amp;rsquo;ve learned about reading serious literature in quantity is that most books are phenomenally forgettable. Tiago Forte, in &amp;ldquo;Building a Second Brain&amp;rdquo;, talks about saving quotations from every book you read, either on index cards or digitally - and when you&amp;rsquo;re reading so much, thats literally all you can do to try and keep track of the different ideas presented to you.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Tue, 31 Dec 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/2024/12/2024-in-review-100-books/</guid></item><item><title>New year, new platform</title><link>https://codeexplainer.wordpress.com/2024/12/30/new-year-new-platform/</link><description>I have decided to move my content to a different platform &amp;#8211; details to follow very soon.</description><author>Code Explainer</author><pubDate>Tue, 31 Dec 2024 08:11:48 GMT</pubDate><guid isPermaLink="true">https://codeexplainer.wordpress.com/2024/12/30/new-year-new-platform/</guid></item><item><title>App defaults entering into 2025</title><link>https://utf9k.net/blog/app-defaults-entering-into-2025/</link><description>Here are my app defaults that I'm bringing with me into 2025</description><author>utf9k</author><pubDate>Tue, 31 Dec 2024 07:10:00 GMT</pubDate><guid isPermaLink="true">https://utf9k.net/blog/app-defaults-entering-into-2025/</guid></item><item><title>A quick review of 2024</title><link>https://martinrue.com/2024-review</link><description>In 2024 I lived in 3 countries and 5 different cities. My favourite is Bangkok. Da Nang is a very close second.</description><author>Martin Rue</author><pubDate>Tue, 31 Dec 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/2024-review</guid></item><item><title>2024.12.DisappearingMoment</title><link>https://newsletter.disappearingmoment.com/archive/306a5245-c590-4c6a-a1c8-df9c6eccd553/</link><description>&lt;p&gt;This month, a lot of people got worked up about the possibility of &lt;a href="https://apnews.com/article/drones-new-jersey-what-to-know-e6f565f5d51d9d47ad140e7e7d131842" target="_blank"&gt;mystery drones&lt;/a&gt; over New Jersey. We made &lt;a href="https://www.bbc.com/culture/article/20231027-behind-the-broadcast-orson-welles-on-the-mass-hysteria-of-the-war-of-the-worlds" target="_blank"&gt;Orson Welles comparisons&lt;/a&gt;. The New York Times reported on &lt;a href="https://www.nytimes.com/2024/12/24/nyregion/new-jersey-new-york-drones.html" target="_blank"&gt;drone fever&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;A U.S. Representative said that the drones were Iran’s doing. New Jersey's best political reporter mewled that this "&lt;a href="https://www.politico.com/newsletters/new-jersey-playbook/2024/12/12/new-jerseys-mothership-connection-00193903?nname=new-jersey-playbook&amp;amp;nid=0000014f-1646-d88f-a1cf-5f46b6de0000&amp;amp;nrid=23bb7b1e-ce83-40c0-bccc-6339e865fe4d&amp;amp;nlid=630315" target="_blank"&gt;theory sounds possible&lt;/a&gt;". He wrote that there was "an absence of other plausible theories”. (&lt;a href="https://www.politico.com/newsletters/new-jersey-playbook/2024/12/16/the-mothership-rejection-00194436?nname=new-jersey-playbook&amp;amp;nid=0000014f-1646-d88f-a1cf-5f46b6de0000&amp;amp;nrid=23bb7b1e-ce83-40c0-bccc-6339e865fe4d&amp;amp;nlid=630315" target="_blank"&gt;Matt Friedman admitted his error&lt;/a&gt; four days later.)&lt;/p&gt;
&lt;p&gt;The United States Department of Homeland Security got involved. So did the Federal Bureau of Investigation. And the Department of Defense. Along with the Federal Aviation Administration, these three issued &lt;a href="https://www.faa.gov/newsroom/dhs-fbi-faa-dod-joint-statement-ongoing-response-reported-drone-sightings" target="_blank"&gt;a joint statement on drone sightings&lt;/a&gt;. The drones at night were loud and bright deep in the heart of Essex. You can read all about it in the New Jersey Office of Homeland Security and Preparedness &lt;a href="https://www.njohsp.gov/threat-landscape/drones-and-unmanned-aircraft-systems/new-jersey-uas-drone-incidents-faq" target="_blank"&gt;Drone Incidents FAQ&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;In the mid-90s, I loved a quote by Sam Donaldson or Tom Brokaw. “In place of truth, we have created facts.” My update: In place of facts, we have created FAQs. &lt;/p&gt;
&lt;p&gt;I'm writing about this to document &lt;a href="https://www.merriam-webster.com/wordplay/before-times-covid-history-and-usage" target="_blank"&gt;the before times&lt;/a&gt;. A month from now, we will miss what it is like now. We will be sentimental, furious, terrified, tortured. We will wonder how we permitted coaxial atrocities. &lt;/p&gt;
&lt;p&gt;When we look back, if we are able to look back, we should know who we were immediately before the collapse. We wanted answers to questions we stumbled to articulate. We believed what we felt rather than what was true.&lt;/p&gt;
&lt;p&gt;Truth is a tiny, silent, piceous drone. Fungible and uninteresting. You don’t know it was there until you’re stuck with undeniable, retrospective evidence. &lt;/p&gt;
&lt;p&gt;Welcome to December 2024’s Disappearing Moment, an inventory of my experiences. I hope you enjoy it.&lt;/p&gt;
&lt;h2&gt;Podcasts&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.americanapartners.com/" target="_blank"&gt;Americana Partners&lt;/a&gt;’ Weekly Market Update Replay (Worth My Time): &lt;a href="https://www.americanapartners.com/ap-team/david-darst/" target="_blank"&gt;David M. Darst&lt;/a&gt; is my Walter Cronkite. I listen to find out what others consider important. And reassure myself that I haven't missed anything.&lt;/p&gt;
&lt;h2&gt;Nerdy Software&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://m4a-to-mp3-converter.vercel.app/" target="_blank"&gt;Convert M4A to MP3&lt;/a&gt; in your browser if you’re feeling too lazy to use &lt;a href="https://www.ffmpeg.org/" target="_blank"&gt;ffmpeg&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Free Font&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://b612-font.com/" target="_blank"&gt;B612&lt;/a&gt;: Optimize the overall homogeneity of your cockpit.&lt;/p&gt;
&lt;h2&gt;Bougie Products&lt;/h2&gt;
&lt;p&gt;I’m excited about my &lt;a href="https://bellsofsteel.us/products/viking-press" target="_blank"&gt;Viking Press&lt;/a&gt; and T-Bar Row attachments. There were some exercises I couldn't do at home. Now I can.&lt;/p&gt;
&lt;h2&gt;Personal Finance and Investing&lt;/h2&gt;
&lt;p&gt;Nisiprius is my favorite &lt;a href="https://www.bogleheads.org/index.php" target="_blank"&gt;Boglehead&lt;/a&gt;. &lt;a href="https://www.bogleheads.org/forum/viewtopic.php?f=9&amp;amp;t=437367&amp;amp;newpost=7999590" target="_blank"&gt;You should read his posts&lt;/a&gt;, starting with &lt;a href="https://www.bogleheads.org/forum/viewtopic.php?t=79939" target="_blank"&gt;the one about risk tolerance&lt;/a&gt;. Then pick from &lt;a href="https://www.bogleheads.org/forum/search.php?keywords=&amp;amp;terms=all&amp;amp;author=nisiprius&amp;amp;sc=1&amp;amp;sf=all&amp;amp;sr=posts&amp;amp;sk=t&amp;amp;sd=d&amp;amp;st=0&amp;amp;ch=750&amp;amp;t=0&amp;amp;submit=Search" target="_blank"&gt;over 53,000 others&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Laura Olin, “&lt;a href="https://www.lauraolin.com/you-dont-actually-have-to-stay-on-twitter/" target="_blank"&gt;You don’t actually have to stay on Twitter.&lt;/a&gt;” (A Personal Favorite): I think less of anyone who does. &lt;/li&gt;
&lt;li&gt;Melanie Sumner, “&lt;a href="https://melsumner.github.io/living-with-adhd" target="_blank"&gt;Living with ADHD&lt;/a&gt;” (I Liked It): Sometimes I struggle to "organize the stuff (I) need to do in a way that makes sure it gets done."&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Some Topics I Intend to Write About in 2025&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cryptocurrency. Learn from my mistakes.&lt;/li&gt;
&lt;li&gt;Dating a long-term spouse or partner.&lt;/li&gt;
&lt;li&gt;Democracy.&lt;/li&gt;
&lt;li&gt;Eating. Exercising.&lt;/li&gt;
&lt;li&gt;Investments that Beth and I have in our accounts. For transparency and accountability.&lt;/li&gt;
&lt;li&gt;Music.&lt;/li&gt;
&lt;li&gt;Running. I like writing about the professionals.&lt;/li&gt;
&lt;li&gt;Skincare.&lt;/li&gt;
&lt;li&gt;Values that matter to me as a librarian.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you for spending a few moments with me. I appreciate you and look forward to corresponding again next month.&lt;/p&gt;
&lt;p&gt;Brett&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want to discuss any of the topics in this newsletter or anything else with other Disappearing Moment readers? Please sign up for &lt;a href="https://august.disappearingmoment.com/" target="_blank"&gt;Perpetual August&lt;/a&gt;. I think it might be fun.&lt;/em&gt;&lt;/p&gt;</description><author>Disappearing Moment</author><pubDate>Tue, 31 Dec 2024 05:19:27 GMT</pubDate><guid isPermaLink="true">https://newsletter.disappearingmoment.com/archive/306a5245-c590-4c6a-a1c8-df9c6eccd553/</guid></item><item><title>Gambling with language models</title><link>https://yehudacohen.substack.com/p/gambling-with-language-models</link><description>One clueless investor's attempt at beating the stock market with ModernBert</description><author>Fun With The Cloud</author><pubDate>Tue, 31 Dec 2024 05:17:20 GMT</pubDate><guid isPermaLink="true">https://yehudacohen.substack.com/p/gambling-with-language-models</guid></item><item><title>Challenges Building an Electron App</title><link>https://www.danielcorin.com/posts/2024/challenges-building-an-electron-app/</link><description>Challenges Building an Electron App</description><author>Thought Eddies</author><pubDate>Tue, 31 Dec 2024 03:06:55 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2024/challenges-building-an-electron-app/</guid></item><item><title>Collective Effervescence</title><link>https://www.aswathkrishnan.com/2024/12/collective-effervescence.html</link><description>&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEgUmI4OfCsdJJ4bWhI1p0Tp10eSLeV19tNu3079YYGLpAuHi5iC412G-s21nC_p2XB60lj3wl-MmJkqiSFwkWI39Nd7vL7gOy4C-AzI77jDU5Secpc432AUfQ_eliD5btaE98xU5fyhmy_AUjwtINhPVvpYHTLVlERZHq4ab6yU6PmcymFV3Y7VPnvMyVYk" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="280" src="https://blogger.googleusercontent.com/img/a/AVvXsEgUmI4OfCsdJJ4bWhI1p0Tp10eSLeV19tNu3079YYGLpAuHi5iC412G-s21nC_p2XB60lj3wl-MmJkqiSFwkWI39Nd7vL7gOy4C-AzI77jDU5Secpc432AUfQ_eliD5btaE98xU5fyhmy_AUjwtINhPVvpYHTLVlERZHq4ab6yU6PmcymFV3Y7VPnvMyVYk=w421-h280" width="421" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;I feel a rush of energy whenever I'm in NYC.&amp;nbsp;&lt;p&gt;&lt;/p&gt;&lt;p&gt;It hits me the moment I step out of the airport, and it's there every time I leave my hotel or brother’s apartment. NYC has majestic buildings, thriving retail, and jam-packed roads. But the main catalyst for making it feel so alive is its people — Pedestrians. Everywhere.&lt;/p&gt;&lt;p&gt;When I first left home to attend university in Singapore, my uncle (who had lived abroad before) wisely advised me to go to crowded places if I felt lonely—and it really helped. But my hostel didn’t have crowded spaces, especially after dark. Sure, you can go to malls, restaurants, or events, but those aren’t practical everyday activities. I find Seattle and most suburbs or even cities in the US to be similar. You barely see people in the streets; just cars or an eerie (serene, if you prefer) emptiness. My mom is often surprised by how she doesn't run into a single soul, save the birds and rabbits, during her daily walks in the neighborhood.&amp;nbsp;&lt;/p&gt;&lt;p&gt;But NYC? That's a rare and different universe. People are always out and about. Different faces, styles, and walks of life, all moving along briskly with purpose. Even as a stranger among them, even without speaking to anyone, their energy rubs off on me.&lt;/p&gt;&lt;p&gt;It’s hard to explain what is happening. There’s a kind of primal comfort in just seeing other humans. The very presence of others, living and breathing and going about their day, creates a kind of ambient belonging and recharges you. It’s like everyone’s a tuning fork vibrating in resonance with each other and the city’s energy, and amplifying it. You’re not just an isolated self anymore; you’re absorbed into something bigger, a shared rhythm, an unscripted orchestra of humanity— a collective flow state.&lt;/p&gt;&lt;p&gt;I know I'm not the only one who craves this energy. And I recognize it isn't all sunshine and roses - the lack of personal space, the incessant noise, and the crowds can wear you down too. I find silence and serenity of Seattle rejuvenating after a trip to NYC. But does it have to be a binary choice between suburban sprawl and overcrowded cities? Surely we can design more places to be walkable, vibrant, and brimming with the hum of humanity, without completely losing individual space or peacefulness? Places where you sense life happening all around you, without derailing your own.&amp;nbsp;&lt;/p&gt;&lt;p&gt;I hope so, because what makes us flourish is not just living in a place, but feeling like the place itself is alive.&lt;/p&gt;</description><author>Aswath Krishnan</author><pubDate>Tue, 31 Dec 2024 02:50:16 GMT</pubDate><guid isPermaLink="true">https://www.aswathkrishnan.com/2024/12/collective-effervescence.html</guid></item><item><title>Generating a triangular navigation mesh from H3 hexagons in R</title><link>https://jonathanchang.org/blog/hexes-to-triangles-h3-r-sf/</link><description>&lt;h2&gt;Introduction&lt;/h2&gt;
        &lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Navigation_mesh"&gt;navigation mesh&lt;/a&gt; is a
          data structure used to aid in pathfinding around obstacles. Originally
          used for video games and robotics, we can also apply the concept of this
          navigational mesh to the movement of animals through landscape, using
          methods such as &lt;a href="https://elifesciences.org/articles/61927"&gt;FEEMS&lt;/a&gt;.&lt;/p&gt;
        &lt;p&gt;Consider the following landscape&lt;sup class="footnote-ref"&gt;&lt;a href="#fn1" id="fnref1"&gt;1&lt;/a&gt;&lt;/sup&gt;:&lt;/p&gt;
        &lt;p&gt;&lt;img alt="" src="/uploads/2024/navmesh1.png" /&gt;&lt;/p&gt;
        &lt;p&gt;Finding a path from starting point &lt;em&gt;s&lt;/em&gt; to goal point &lt;em&gt;t&lt;/em&gt; is
          computationally challenging, as there are many possible routes one might
          take between these two endpoints, and the possibility space is so large
          that it can be challenging to efficiently compute a fast path. Instead,
          we can simplify the space by shrinking the possible locations in the
          landscape to a series of &lt;em&gt;nodes&lt;/em&gt;, and the neighboring nodes that you can
          move to are connected by &lt;em&gt;edges&lt;/em&gt;. The resulting &lt;em&gt;mesh&lt;/em&gt; typically looks
          like a bunch of triangles overlapping the landscape:&lt;/p&gt;
        &lt;p&gt;&lt;img alt="" src="/uploads/2024/navmesh2.png" /&gt;&lt;/p&gt;
        &lt;p&gt;In this blog post I’ll discuss how to construct such a mesh using
          real-world shapefiles and the &lt;code&gt;h3&lt;/code&gt; library in R, which tessellates
          hexagons across the globe at several resolutions. I also looked into
          alternatives such as
          &lt;a href="https://cran.r-project.org/package=dggridR"&gt;dggridR&lt;/a&gt; but I found other
          packages to not be satisfactory for a number of reasons (mostly speed).&lt;/p&gt;
        &lt;p&gt;To begin with, why h3, and why hexagons? The answer to this is that it
          is very convenient to have a grid system that can be used for any place
          on the planet, and having a variety of different resolutions makes it
          convenient to analyze spatial data on scales ranging from
          continent-level to city-level. Hexagons in particular have a nice
          property where the distance from the center of one hexagon to its
          neighboring hex is roughly equal.&lt;sup class="footnote-ref"&gt;&lt;a href="#fn2" id="fnref2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
        &lt;p&gt;&lt;img alt="" src="/uploads/2024/h3-hex.png" /&gt;&lt;/p&gt;
        &lt;h2&gt;Worked example&lt;/h2&gt;
        &lt;p&gt;First, load some required packages, including &lt;code&gt;h3jsr&lt;/code&gt;, which I found to
          have a nicer interface than other h3 libraries for R.&lt;/p&gt;
        &lt;div class="language-r highlighter-rouge"&gt;
          &lt;div class="highlight"&gt;
            &lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tidyverse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h3jsr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;library&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;requireNamespace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"maps"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;p&gt;For today’s example we’ll be using a simple map showing some counties in
          the San Francisco Bay Area (SFBA). We need to turn off spherical
          geometry in &lt;code&gt;sf&lt;/code&gt; since at this scale, everything is approximately planar
          anyway and there are some issues with the geometries provided in the
          &lt;code&gt;maps&lt;/code&gt; package.&lt;sup class="footnote-ref"&gt;&lt;a href="#fn3" id="fnref3"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
        &lt;div class="language-r highlighter-rouge"&gt;
          &lt;div class="highlight"&gt;
            &lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;sf&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;sf_use_s2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;bay_counties&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;“alameda”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;“contra costa”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;“marin”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;“napa”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;“san mateo”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;“santa clara”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;“solano”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;“sonoma”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;“san francisco”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/p&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;sfba&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maps&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;‘county’&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;paste0&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;“california,”&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bay_counties&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;plot&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;st_as_sf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;st_transform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;4326&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;st_make_valid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&lt;/p&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;ggplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sfba&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;geom_sf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;theme_minimal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/p&gt;
      &lt;p&gt;&lt;img alt="" src="/uploads/2024/sfba-counties-1.png" /&gt;&lt;!-- --&gt;&lt;/p&gt;
      &lt;p&gt;Next, we need to find the h3 hexagons that intersect this shapefile of
        the SFBA. I’d like my hexagons to cover a couple of square kilometers,
        and this &lt;a href="https://h3geo.org/docs/core-library/restable/#average-area-in-km2"&gt;corresponds to roughly resolution
          7&lt;/a&gt; in
        h3. We must first dissolve the individual features (i.e., remove
        internal borders) then use the &lt;code&gt;polygon_to_cells&lt;/code&gt; function to identify
        the correct h3 cells that intersect with our SFBA borders.&lt;/p&gt;
      &lt;div class="language-r highlighter-rouge"&gt;
        &lt;div class="highlight"&gt;
          &lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;dissolved_sfba&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;summarise&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sfba&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;st_union&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;geom&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;polygon_to_cells&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dissolved_sfba&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)[[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;div class="highlighter-rouge"&gt;
        &lt;div class="highlight"&gt;
          &lt;pre class="highlight"&gt;&lt;code&gt;## [1] "872830311ffffff" "872830925ffffff" "87283154dffffff" "872836a62ffffff"
## [5] "872830314ffffff" "872830928ffffff"
&lt;/code&gt;&lt;/pre&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;Plot these h3 hexes to get an idea of what we’re working with.&lt;/p&gt;
      &lt;div class="language-r highlighter-rouge"&gt;
        &lt;div class="highlight"&gt;
          &lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;ggplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cell_to_polygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;geom_sf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;theme_minimal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
        &lt;/div&gt;
      &lt;/div&gt;
      &lt;p&gt;&lt;img alt="" src="/uploads/2024/sfba-hexes-1.png" /&gt;&lt;!-- --&gt;&lt;/p&gt;
      &lt;p&gt;For each cell ID, we need to identify its neighbors using the &lt;code&gt;get_disk&lt;/code&gt;
        function with &lt;code&gt;distance = 1&lt;/code&gt;. &lt;a href="https://obrl-soil.github.io/h3jsr/reference/get_disk.html"&gt;This function’s
          documentation&lt;/a&gt;
        says:&lt;/p&gt;
      &lt;blockquote&gt;
        &lt;p&gt;The first address returned is the input address, the rest follow in a
          spiral anticlockwise order.&lt;/p&gt;
      &lt;/blockquote&gt;
      &lt;p&gt;This is perfect for our use case. We take the origin hex and find its
        center. Then do the same for two of its neighbors, and construct a
        triangle between the centers of these three hexes.&lt;/p&gt;
      &lt;div class="language-r highlighter-rouge"&gt;
        &lt;div class="highlight"&gt;
          &lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;&lt;/p&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;first_three&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;first_triangle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell_to_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_three&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;st_combine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;st_cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;“POLYGON”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;st_as_sf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;&lt;/p&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;ggplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first_triangle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;geom_sf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;theme_minimal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/p&gt;
    &lt;p&gt;&lt;img alt="" src="/uploads/2024/sfba-single-triangle-1.png" /&gt;&lt;!-- --&gt;&lt;/p&gt;
    &lt;p&gt;We can repeat this procedure to construct triangles between the centers
      of all the hexes surrounding our origin hex. We exclude the origin point
      from the loop and also add in the first neighboring hex in the ring,
      otherwise we’ll only have five triangles, not six, around the origin
      hex.&lt;/p&gt;
    &lt;div class="language-r highlighter-rouge"&gt;
      &lt;div class="highlight"&gt;
        &lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;wrapped_vec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapped_vec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell_to_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wrapped_vec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wrapped_vec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="m"&gt;+1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;st_combine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;st_cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;“POLYGON”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;st_as_sf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/p&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;tris&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bind_rows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;mutate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/p&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;ggplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tris&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;aes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;geom_sf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;theme_minimal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/p&gt;
  &lt;p&gt;&lt;img alt="" src="/uploads/2024/sfba-multi-triangle-1.png" /&gt;&lt;!-- --&gt;&lt;/p&gt;
  &lt;p&gt;We can build off of this initial work to write a function that will take
    an &lt;code&gt;h3&lt;/code&gt; id as input and return a triangular mesh.&lt;/p&gt;
  &lt;div class="language-r highlighter-rouge"&gt;
    &lt;div class="highlight"&gt;
      &lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;to_triangles&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;get_disk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ring_size&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)[[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;wrapped_vec&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;disk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;lapply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapped_vec&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;tri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wrapped_vec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wrapped_vec&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;ii&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;cell_to_point&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tri&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;TRUE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;st_combine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;st_cast&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"POLYGON"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="n"&gt;st_as_sf&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bind_rows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;p&gt;Use &lt;code&gt;lapply&lt;/code&gt; and &lt;code&gt;bind_rows&lt;/code&gt; to construct an entire &lt;code&gt;sf&lt;/code&gt; dataframe that
    contains the entire triangular mesh. There are a lot of duplicates but
    we can just use &lt;code&gt;distinct&lt;/code&gt; to get rid of these. (I use
    &lt;code&gt;parallel::mclapply&lt;/code&gt; here as this step can be a bit slow.)&lt;/p&gt;
  &lt;div class="language-r highlighter-rouge"&gt;
    &lt;div class="highlight"&gt;
      &lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;all_tris&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;mclapply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;to_triangles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;mc.cores&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;bind_rows&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;%&amp;gt;%&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;distinct&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;ggplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;all_tris&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;geom_sf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;“white”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;theme_minimal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/uploads/2024/sfba-tri-mesh-1.png" /&gt;&lt;!-- --&gt;&lt;/p&gt;
&lt;p&gt;Observant readers will note that these triangle meshes will include
  nodes that are out in the water or in neighboring counties, since the
  hexes at the edge of our SFBA shapefile will inevitably have a few
  neighbors that are not contained within the SFBA shapefile.&lt;/p&gt;
&lt;p&gt;While it wasn’t necessary to do so for my analysis, these can easily be
  removed by using a binary predicate such as &lt;code&gt;st_contains&lt;/code&gt;, to never
  include any edge that enters the water or otherwise exits the boundary
  of the shapefile.&lt;/p&gt;
&lt;div class="language-r highlighter-rouge"&gt;
  &lt;div class="highlight"&gt;
    &lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;contain_result&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;st_contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dissolved_sfba&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all_tris&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;contained_mesh&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;all_tris&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;unlist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;contain_result&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;ggplot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;geom_sf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dissolved_sfba&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"pink"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;geom_sf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;contained_mesh&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"white"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;theme_minimal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;img alt="" src="/uploads/2024/sfba-tri-mesh-no-water-1.png" /&gt;&lt;!-- --&gt;&lt;/p&gt;
&lt;p&gt;This navigational mesh can now be saved using functions such as
  &lt;code&gt;write_sf&lt;/code&gt; and used in downstream analysis software.&lt;/p&gt;
&lt;h2&gt;Exercises&lt;/h2&gt;
&lt;ol&gt;
  &lt;li&gt;How can we avoid the creation of duplicate triangle meshes?&lt;/li&gt;
  &lt;li&gt;Use a &lt;code&gt;compact&lt;/code&gt; representation to generate a triangle mesh with
    simplified interiors.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="language-r highlighter-rouge"&gt;
  &lt;div class="highlight"&gt;
    &lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;cell_to_polygon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h3jsr&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;compact&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;simple&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;FALSE&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;p&gt;&lt;/span&gt;&lt;span class="n"&gt;ggplot&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;geom_sf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;dissolved_sfba&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;NA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;geom_sf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;NA&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;“red”&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;theme_minimal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/uploads/2024/sfba-compacted-1.png" /&gt;&lt;!-- --&gt;&lt;/p&gt;
&lt;section class="footnotes"&gt;
  &lt;ol&gt;
    &lt;li id="fn1"&gt;
      &lt;p&gt;Figures taken from &lt;a href="https://www.cs.umd.edu/class/spring2018/cmsc425/Lects/lect15-nav-mesh.pdf"&gt;these lecture
          notes&lt;/a&gt;. &lt;a class="footnote-backref" href="#fnref1"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn2"&gt;
      &lt;p&gt;For more detail, see &lt;a href="https://www.uber.com/blog/h3/"&gt;Uber’s blog post introducing the h3
          system&lt;/a&gt;. &lt;a class="footnote-backref" href="#fnref2"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
    &lt;li id="fn3"&gt;
      &lt;p&gt;For your own analyses, you should probably use shapefiles that can
        be correctly handled by &lt;code&gt;sf&lt;/code&gt;. &lt;a class="footnote-backref" href="#fnref3"&gt;↩&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/section&gt;</description><author>Jonathan Chang</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jonathanchang.org/blog/hexes-to-triangles-h3-r-sf/</guid></item><item><title>What You Are Paying For</title><link>http://jeremymikkola.com/posts/2024_12_31_what_you_are_paying_for.html</link><description>&lt;div class="info"&gt;
    Posted on December 31, 2024
    
&lt;/div&gt;

&lt;p&gt;When you go into your local burger shop and buy a burger, what are you paying for? One way to look at it is that you are paying for the food, for the convenience, for the experience - you are paying for what you get out of it.&lt;/p&gt;
&lt;p&gt;You can also think about the things that are necessary to create the product you bought. There’s the beef and the buns. There’s also the labor that goes into making that burger. If you want to really pull apart the business you can list all sorts of things you are also paying for: the rent on the space the restaurant is in, their taxes, insurance, interest on loans, utilities, grease disposal, cleaning, advertising, repairs, accounting, shipping, spoilage, management, banking and payment services, toilet paper, and maybe a paper-thin slice of profit on top.&lt;/p&gt;
&lt;p&gt;That mental model feels a lot more detailed. You could list out where every thousandth of a cent of that burger’s price ultimately goes. But that model misses a dimension: it misses why those elements cost what they do.&lt;/p&gt;
&lt;p&gt;Take rent, for example. A big factor in rent costs is supply and demand. A big factor in the supply side is how much real estate the city will allow to be zoned commercially, and what restrictions they place on it. Laws intended to keep a city looking nice, like limiting the height of buildings, have a side effect of increasing rent. When you go to one of those hip locations to buy your burger, some of what you are paying for is the fact that the surrounding area is nice to be in. When you buy a burger you are buying the outcome of zoning decisions.&lt;/p&gt;
&lt;p&gt;You are also paying for that location to continue to be a burger shop instead of turning into a Chase bank or Starbucks.&lt;/p&gt;
&lt;p&gt;Let’s say that just down the street from the local burger shop is a franchise of the large national fast food chain of your choice. What are you paying for if you eat there, instead? Obviously you are paying for a slightly different mix of many of the same costs associated with the local place. The meat might be a bit cheaper but now there’s landscaping and power washing costs (this location has a drive-through). Since this location is a franchise of a chain, some slice of the pie also goes to that national chain. What does that buy?&lt;/p&gt;
&lt;p&gt;It buys use of the brand name, it buys access to the logistics chain, and it buys advertising. It also buys whatever else that national chain decides to spend their remaining money on. In the corporate headquarters of every burger-selling chain out there, there will be someone whose job it is to handle expansion. Someone in some office building sits and makes decisions about whether a given site is good for a new location. When you buy a burger from your local instantiation of this burger empire, you are in effect buying more locations of that chain. Those new locations are likely somewhere hundreds or thousands of miles away and of little value to you.&lt;/p&gt;
&lt;p&gt;When you pay for a SaaS service, part of what you are paying for is feature development. Whether you are paying for the feature development that already happened in the past, the feature development that is currently happening, or the feature development that will be done in the future… is left as an exercise for the reader. What it does not pay for is the development of features that are all aimed at you. Some of that development may go to make your life better: a thing runs a bit faster or has another bell and whistle. Some of that development is aimed more internally to the company: they make something cheaper for them to run, page them in the middle of the night less often, or have fewer security holes. But, like the national burger chain, some of it will be aimed at expansion. They will develop new features, spin up new teams, perhaps even create entire new product lines (or acquire other businesses) for the sake of expanding. They might start building the features that they need to have to win the big enterprise user, features that might be utterly useless to you. And yet part of what you are buying when you pay for that service is their ability to acquire those customers.&lt;/p&gt;
&lt;p&gt;This entire line of thinking becomes practically useful when flipped on its head. Instead of thinking about the fact that buying a burger buys a nice neighborhood, one can follow the logic in the other direction and see that laws and policies that lead to nice neighborhoods will reduce the supply of commercial real estate, increasing the price of rent, and thus cause burgers to get more expensive. Expanding to enterprise users means giving your small users a bit less for their money. Every action has externalities, and every decision has costs. Things following the pattern of “you cannot have your cake and eat it too” (or “there’s no such thing as a free lunch”) are the rule not the exception.&lt;/p&gt;</description><author>jeremymikkola.com</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://jeremymikkola.com/posts/2024_12_31_what_you_are_paying_for.html</guid></item><item><title>2024 Year in Review</title><link>https://benovermyer.com/blog/2024/12/2024-year-in-review/</link><description>&lt;p&gt;2024 was a year of travel, challenges, endings, and beginnings. I went on five trips, four of which were for recreation and the fifth of which was almost a move to a different state. My fitness and health suffered, and Silver Gryphon Games shut down. However, I began taking music lessons, won an award at work, and regularly ran an RPG campaign for once.&lt;/p&gt;
&lt;h2 id="winning-an-award-at-apiture"&gt;Winning an Award at Apiture&lt;/h2&gt;
&lt;p&gt;At my day job, I won an award for my work on Apiture’s hosted sites service. I continued to be the sole infrastructure person for hosted sites, despite their quantity at the end of the year. My automation for them made it much easier for the designers to spin up new sites without requiring work from the platform engineering team.&lt;/p&gt;
&lt;h2 id="collapse-of-exercise-habit"&gt;Collapse of Exercise Habit&lt;/h2&gt;
&lt;p&gt;My exercise habit fell apart. I started the year strong, going to the gym regularly and focusing on a bodybuilding routine that was slowly starting to show progress. Then, I went to my annual physical, and the doctor was less than enthusiastic about my chosen exercise path. For some reason, I let that discourage me from exercising at all the rest of the year.&lt;/p&gt;
&lt;p&gt;Towards the end of the year, though, I started going for longer walks occasionally. I have it as a daily to-do item, but I'm not consistent in doing it daily yet.&lt;/p&gt;
&lt;h2 id="alcohol-free-months"&gt;Alcohol-Free Months&lt;/h2&gt;
&lt;p&gt;At the beginning of the year I set out to have three full alcohol-free months in 2024. I achieved two full months and two half months. That is sort of a success, I guess, but I think I can do better. Next year will have a more ambitious goal. My interest in going alcohol-free has increased.&lt;/p&gt;
&lt;p&gt;I've attempted to completely quit drinking a couple times before. This is not that, not yet. I can see this evolving into that though. Past attempts were not based on a holistic lifestyle change. Now, going without alcohol is an accessory to other changes I have made or am planning to make.&lt;/p&gt;
&lt;h2 id="the-death-of-silver-gryphon-games"&gt;The Death of Silver Gryphon Games&lt;/h2&gt;
&lt;p&gt;While we tried to resurrect the company, neither Kevin nor I had the time or motivation to work on it. I formally shuttered the business in December. The second editions of both Aether and Ingenium will never be published, sadly. That’s too bad, because the cover art for both was awesome, and they fixed a lot of the problems with the first editions. However, a business needs sustained work in order to function, and that won’t happen for SGG.&lt;/p&gt;
&lt;h2 id="journaling-about-trips"&gt;Journaling About Trips&lt;/h2&gt;
&lt;p&gt;In 2024 I went on three major trips and I promised to journal about all of them. I wrote about the Cancun trip and the cruise, but I did not write about the eclipse trip. I thought I did, but I can’t find a journal entry for it. Two out of three, but not enough to count this goal achieved.&lt;/p&gt;
&lt;p&gt;The journal entries that exist are nice and detailed, though. They'll be a good reminder in days to come.&lt;/p&gt;
&lt;h2 id="journaling-habit"&gt;Journaling Habit&lt;/h2&gt;
&lt;p&gt;While I did keep a regular work journal each week, I didn’t write a regular personal journal. Part of the problem is that I never set up the habit like I did with the work journal; there was no specific trigger. This is something I can fix in 2025.&lt;/p&gt;
&lt;p&gt;I did start keeping a gaming journal just a few days ago. If I keep up with this, then I will have four different journals that I update: a personal journal, a work journal, a gaming journal, and a spiritual journal. Perhaps if I write regularly for all of them, that will start to bleed through into blogging, and I'll update this site more frequently.&lt;/p&gt;
&lt;h2 id="books-read"&gt;Books Read&lt;/h2&gt;
&lt;p&gt;I didn’t read nearly as many books in 2024 as I did in 2023. Also, the variety was less, with 8 different authors in 2024 and 11 in 2023. I’m not sure why, but I imagine losing almost a full month of the year to trips where I didn’t read at all didn’t help.&lt;/p&gt;
&lt;p&gt;My queue of books to read is growing. I have quite a few on my shelves. I may hold off on buying any more books until I've worked through that backlog a bit.&lt;/p&gt;
&lt;h2 id="rpg-campaign"&gt;RPG Campaign&lt;/h2&gt;
&lt;p&gt;In November I started running a Dungeons &amp;amp; Dragons campaign in-person for a few random people. It is now the longest-running regular campaign I’ve ever run. It’s also the most difficult to run, as several of the players remember throwaway comments I make about setting flavor and accept that as canon. This means I have to take notes on everything I decide. On the other hand, though, these notes are making it easier to plan sessions and review what happened in past sessions.&lt;/p&gt;
&lt;p&gt;The next session will be on January 6, after a several-week break for the holidays.&lt;/p&gt;
&lt;h2 id="beginning-of-music-lessons"&gt;Beginning of Music Lessons&lt;/h2&gt;
&lt;p&gt;In December I began taking guitar lessons. This is the first time since I was a teenager that I’ve taken music lessons. I’ve been good about practicing almost every day, too; the only days I haven’t practiced were Christmas Eve and Christmas Day.&lt;/p&gt;
&lt;p&gt;I've been practicing the usual beginner songs from a Hal Leonard book. As I get better, I want to try a few different styles, including Spanish guitar. I have a list of songs to attempt once I get good enough.&lt;/p&gt;
&lt;h2 id="previous-years"&gt;Previous Years&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2023/12/2023-year-in-review/"&gt;2023&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2022/12/2022-year-in-review/"&gt;2022&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2021/12/2021-year-in-review/"&gt;2021&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2020/12/2020-year-in-review/"&gt;2020&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2018/12/2018-year-in-review/"&gt;2018&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2017/01/looking-back-on-2016-and-forward-to-2017/"&gt;2016&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2016/01/2015-year-in-review/"&gt;2015&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://benovermyer.com/blog/2013/12/2013-year-in-review/"&gt;2013&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Ben Overmyer's Site</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benovermyer.com/blog/2024/12/2024-year-in-review/</guid></item><item><title>Building for and debugging an ARM Cortex-M</title><link>http://blog.peramid.es/rss.xml/posts/2024-12-31-arm.html</link><description>&lt;p&gt;I recently bought an &lt;em&gt;ST NUCLEO-L432KC&lt;/em&gt;, a small dev board with an &lt;a href="https://www.st.com/en/evaluation-tools/nucleo-l432kc.html"&gt;STM32L432KC&lt;/a&gt;, a Cortex-M4 micro-controller designed for ultra-low-power applications.&lt;a class="footnote-ref" href="#fn1" id="fnref1"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; These STM32 MCUs come with a proprietary tool for code generation called STM32CubeMX, unfortunately my experience with it was very poor so to speak: I couldn’t even generate a functioning base project for this board.&lt;a class="footnote-ref" href="#fn2" id="fnref2"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Since ST doesn’t provide ready to use template projects I had to look for an alternative. &lt;a href="https://platformio.org/"&gt;PlatformIO&lt;/a&gt; is a popular tool nowadays for these sorts of things. I’ve never used it before but I was able to go from zero to blinking LED in less than 5 minutes, literally. My only issue with it was that this felt like cheating, and I was looking to learn a bit more about how these binaries are built, so I decided to build it “from scratch”.&lt;/p&gt;
&lt;p&gt;First you will need to install a GDB and GCC cross-compiler that can target ARM, the &lt;a href="https://sourceware.org/newlib/"&gt;Newlib&lt;/a&gt; C standard library, and &lt;a href="https://openocd.org/"&gt;OpenOCD&lt;/a&gt;. This will depend on your distro but in Arch Linux you’ll find everything in the official repo:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -Syu arm-none-eabi-gcc arm-none-eabi-newlib arm-none-eabi-gdb openocd&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The next step is to obtain &lt;a href="https://arm-software.github.io/CMSIS_5/latest/General/html/index.html"&gt;CMSIS&lt;/a&gt; Core (standard APIs for Cortex processors), BSP and HAL drivers for our MCU’s family, which is all provided by ST on GitHub:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir nucleo-l432kc
cd nucleo-l432kc
git submodule add --depth 1 https://github.com/STMicroelectronics/cmsis-device-l4.git
git submodule add --depth 1 https://github.com/STMicroelectronics/stm32l4xx-hal-driver.git
git submodule add --depth 1 https://github.com/STMicroelectronics/stm32l4xx-nucleo-32-bsp.git
git submodule add --depth 1 https://github.com/STMicroelectronics/cmsis-core.git&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you will need the device &lt;a href="https://developer.arm.com/documentation/ddi0403/d/System-Level-Architecture/System-Level-Programmers--Model/ARMv7-M-exception-model/The-vector-table"&gt;vector table&lt;/a&gt; (“&lt;em&gt;contains the initialization value for the stack pointer, and the entry point addresses of each exception handler&lt;/em&gt;”) and the linker script for our MCU. You can find these files and also a bunch of basic examples in the &lt;a href="https://github.com/STMicroelectronics/STM32CubeL4"&gt;STM32CubeL4 MCU Firmware Package&lt;/a&gt; repo, which is actually quite heavy, so let’s grab just what we need from there:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone --depth 1 --filter=blob:none --sparse https://github.com/STMicroelectronics/STM32CubeL4.git
cd STM32CubeL4
git sparse-checkout set Projects/NUCLEO-L432KC/Examples/GPIO/GPIO_IOToggle&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The vector table initialisation code is in &lt;code&gt;startup_stm32l432kcux.s&lt;/code&gt;, and the linker script for GNU ld is &lt;code&gt;STM32L432KCUX_FLASH.ld&lt;/code&gt;. This directory includes project files for IDEs we don’t care about, but you should copy the example code inside &lt;code&gt;Src&lt;/code&gt; and &lt;code&gt;Inc&lt;/code&gt; for blinking the green LED in the board.&lt;/p&gt;
&lt;p&gt;For some reason &lt;code&gt;STM32L432KCUX_FLASH.ld&lt;/code&gt; is missing a symbol named &lt;code&gt;__end__&lt;/code&gt; that is needed in &lt;em&gt;newlib&lt;/em&gt;. I haven’t look too much into this but maybe ST’s fork of newlib does something different. In any case, what I have done is to include the following line in the &lt;code&gt;._user_heap_stack&lt;/code&gt; almost at the end of the file:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;. = ALIGN(8);
PROVIDE ( end = . );
PROVIDE ( _end = . );
PROVIDE ( __end__ = . ); /* Add this directive here */
. = . + _Min_Heap_Size;
. = . + _Min_Stack_Size;
. = ALIGN(8);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I will save you the pain of having to figure out what exactly you need to compile this simple project with GCC (you can also find all this stuff in my git repo &lt;a href="https://gitlab.com/trobador/nucleo-l432kc-template"&gt;nucleo-l432kc-template&lt;/a&gt;):&lt;/p&gt;
&lt;div class="sourceCode" id="cb5"&gt;&lt;pre class="sourceCode makefile"&gt;&lt;code class="sourceCode makefile"&gt;&lt;span id="cb5-1"&gt;&lt;a href="#cb5-1" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;CC&lt;/span&gt; &lt;span class="ch"&gt;=&lt;/span&gt;&lt;span class="st"&gt; arm-none-eabi-gcc&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-2"&gt;&lt;a href="#cb5-2" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;DEFS&lt;/span&gt; &lt;span class="ch"&gt;=&lt;/span&gt;&lt;span class="st"&gt; -DSTM32L432xx&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-3"&gt;&lt;a href="#cb5-3" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;CFLAGS&lt;/span&gt; &lt;span class="ch"&gt;=&lt;/span&gt;&lt;span class="st"&gt; -Wall -Wextra -g -march=armv7e-m+fp -mfloat-abi=hard -mfpu=fpv4-sp-d16 --specs=rdimon.specs&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-4"&gt;&lt;a href="#cb5-4" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;LDFLAGS&lt;/span&gt; &lt;span class="ch"&gt;=&lt;/span&gt;&lt;span class="st"&gt; -TSTM32L432KCUX_FLASH.ld&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-5"&gt;&lt;a href="#cb5-5" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;CMSIS_DEVICE&lt;/span&gt; &lt;span class="ch"&gt;=&lt;/span&gt;&lt;span class="st"&gt; cmsis-device-l4/&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-6"&gt;&lt;a href="#cb5-6" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;HAL_DRIVER&lt;/span&gt; &lt;span class="ch"&gt;=&lt;/span&gt;&lt;span class="st"&gt; stm32l4xx-hal-driver/&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-7"&gt;&lt;a href="#cb5-7" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;NUCLEO_BSP&lt;/span&gt; &lt;span class="ch"&gt;=&lt;/span&gt;&lt;span class="st"&gt; stm32l4xx-nucleo-32-bsp/&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-8"&gt;&lt;a href="#cb5-8" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;INCLUDES&lt;/span&gt; &lt;span class="ch"&gt;=&lt;/span&gt;&lt;span class="st"&gt; -Isrc -I&lt;/span&gt;&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;HAL_DRIVER&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;&lt;span class="st"&gt;/Inc -I&lt;/span&gt;&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;NUCLEO_BSP&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;&lt;span class="st"&gt; -Icmsis-core/Include -I&lt;/span&gt;&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;CMSIS_DEVICE&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;&lt;span class="st"&gt;/Include&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-9"&gt;&lt;a href="#cb5-9" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb5-10"&gt;&lt;a href="#cb5-10" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dv"&gt;all:&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-11"&gt;&lt;a href="#cb5-11" tabindex="-1"&gt;&lt;/a&gt;	&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;CC&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt; &lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;CFLAGS&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt; &lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;LDFLAGS&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt; &lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;DEFS&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt; &lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;INCLUDES&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt; &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-12"&gt;&lt;a href="#cb5-12" tabindex="-1"&gt;&lt;/a&gt;	&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;HAL_DRIVER&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;Src/stm32l4xx_hal.c &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-13"&gt;&lt;a href="#cb5-13" tabindex="-1"&gt;&lt;/a&gt;	&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;HAL_DRIVER&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;Src/stm32l4xx_hal_cortex.c &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-14"&gt;&lt;a href="#cb5-14" tabindex="-1"&gt;&lt;/a&gt;	&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;HAL_DRIVER&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;Src/stm32l4xx_hal_gpio.c &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-15"&gt;&lt;a href="#cb5-15" tabindex="-1"&gt;&lt;/a&gt;	&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;HAL_DRIVER&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;Src/stm32l4xx_hal_pwr.c &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-16"&gt;&lt;a href="#cb5-16" tabindex="-1"&gt;&lt;/a&gt;	&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;HAL_DRIVER&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;Src/stm32l4xx_hal_pwr_ex.c &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-17"&gt;&lt;a href="#cb5-17" tabindex="-1"&gt;&lt;/a&gt;	&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;HAL_DRIVER&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;Src/stm32l4xx_hal_rcc.c &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-18"&gt;&lt;a href="#cb5-18" tabindex="-1"&gt;&lt;/a&gt;	&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;NUCLEO_BSP&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;stm32l4xx_nucleo_32.c &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-19"&gt;&lt;a href="#cb5-19" tabindex="-1"&gt;&lt;/a&gt;	&lt;span class="ch"&gt;$(&lt;/span&gt;&lt;span class="dt"&gt;CMSIS_DEVICE&lt;/span&gt;&lt;span class="ch"&gt;)&lt;/span&gt;Source/Templates/system_stm32l4xx.c &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-20"&gt;&lt;a href="#cb5-20" tabindex="-1"&gt;&lt;/a&gt;	startup_stm32l432kcux.s &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-21"&gt;&lt;a href="#cb5-21" tabindex="-1"&gt;&lt;/a&gt;	src/stm32l4xx_it.c &lt;span class="ch"&gt;\&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb5-22"&gt;&lt;a href="#cb5-22" tabindex="-1"&gt;&lt;/a&gt;	src/main.c&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;rdimon&lt;/code&gt; specs file is used for &lt;a href="https://developer.arm.com/documentation/dui0375/g/What-is-Semihosting-/What-is-semihosting-"&gt;semihosting&lt;/a&gt;, which you will need later for debugging. Once you run &lt;code&gt;make&lt;/code&gt; you should get a new &lt;code&gt;a.out&lt;/code&gt; file. You can use the common &lt;code&gt;file&lt;/code&gt; command or &lt;code&gt;arm-none-eabi-objdump -f&lt;/code&gt; to see that the ELF is for an ARM architecture.&lt;/p&gt;
&lt;p&gt;Let’s now flash the MCU with this new file. Plug the dev board to a USB port, then open a new terminal and run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openocd -f interface/stlink.cfg -c &amp;quot;transport select hla_swd&amp;quot; -f target/stm32l4x.cfg&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You should see OpenOCD detecting a Cortex-M4 processor and informing us that GDB is ready to accept new connections on port 3333. Now in another terminal let’s use GDB to flash our device:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;arm-none-eabi-gdb -q a.out

target extended-remote :3333
monitor program a.out verify&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A message indicating that the programming has finished and successfully verified should appear on the screen. We won’t see any blinking LED yet because the state of the MCU is &lt;code&gt;halted&lt;/code&gt; (you can see this by running &lt;code&gt;monitor targets&lt;/code&gt;), so start our program inside GDB do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;monitor reset run&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you should see the small green LED blinking. You can exit GDB with &lt;code&gt;q&lt;/code&gt; and kill OpenOCD with Ctrl-C.&lt;/p&gt;
&lt;p&gt;To finish this tutorial, let’s try some basic debugging capabilities. Open the &lt;code&gt;main.c&lt;/code&gt; source and look for the &lt;code&gt;while&lt;/code&gt; loop where the GPIO port connected to the LED gets toggled. Add a &lt;code&gt;printf("toggled\n");&lt;/code&gt; anywhere inside the loop and include &lt;code&gt;stdio.h&lt;/code&gt; at the to of the file. Now recompile and reprogram the MCU with this new binary. You may notice the green LED turns on but doesn’t blink, this is because the MCU is halted awaiting for a &lt;em&gt;host&lt;/em&gt; to connect. In the GDB session do:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;monitor arm semihosting enable
load
monitor reset run&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point you will see that in the OpenOCD terminal our message gets printed every 100 milliseconds. You can do all sorts of things now in GDB, like seeing the current point of execution with the &lt;code&gt;l&lt;/code&gt; command, or halting the execution and stepping with &lt;code&gt;monitor halt&lt;/code&gt; and the &lt;code&gt;s&lt;/code&gt; command.&lt;/p&gt;
&lt;section class="footnotes footnotes-end-of-document" id="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn1"&gt;&lt;p&gt;As low as ~10 μA in run mode, and in the order of tens of nanoamps in standby mode&lt;a class="footnote-back" href="#fnref1"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li id="fn2"&gt;&lt;p&gt;I won’t go into details here but for instance it just didn’t generate a Makefile for me&lt;a class="footnote-back" href="#fnref2"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>pera's blog</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://blog.peramid.es/rss.xml/posts/2024-12-31-arm.html</guid></item><item><title>Interesting Articles I’ve Read in 2024</title><link>http://bcmullins.github.io/interesting-articles-2024/</link><description>Below is a collection of interesting articles I’ve read in 2024. Three papers are on differential privacy and adjacent topics. There’s a recent method for differentially private SGD utilizing methods from private query answering, an intuitive watermarking scheme for language models, and a paper from 1986 that proposed $k$-anonymity before it was formalized as a criterion for de-identification. Several papers are on the history of ideas ranging from early twentieth century pragmatism, the synergies and antagonisms between poetry and philosophy, and the relation between periodicals and intellectual progress to the deaths of Analytical Marxism and Effective Altruism.</description><author>Brett Mullins</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://bcmullins.github.io/interesting-articles-2024/</guid></item><item><title>Trackmania Tracker</title><link>https://www.werder.space/blog/trackmaniatracker/</link><description/><author>Jan's Space</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.werder.space/blog/trackmaniatracker/</guid></item><item><title>Container load balancing with Linux bridges, veths, and IPVS</title><link>https://monroeclinton.com/container-load-balancing-linux/</link><description>I&amp;rsquo;ve been working on a project that takes requests and load balances them between containers. Using Docker or Kubernetes makes this easy, but I wondered how these systems work, so I decided to try and implement it myself. I&amp;rsquo;ve been using containerd to programmatically create and manage containers. It works quite well for this task, however it does not provide networking to or between containers. This is a problem for the load balancing part of my project, so I searched for a solution.</description><author>Monroe Clinton</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://monroeclinton.com/container-load-balancing-linux/</guid></item><item><title>How I run a coffee club</title><link>http://notes.eatonphil.com/2024-12-31-how-i-run-a-coffee-club.html</link><description>&lt;p&gt;I started the &lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;NYC Systems Coffee
Club&lt;/a&gt; in December
of 2023. It's gone pretty well! I regularly get around 20 people each
month. You bring a drink if you feel like it and you hang out with
people for an hour or two.&lt;/p&gt;
&lt;p&gt;There is no agenda, there is no speaker, there is no structure. The
only "structure" is that when the circle of people talking to each
other seems gets too big, I break the circle up into two smaller circles so
we can get more conversations going.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/coffeeclub.png" src="/assets/coffeeclub.png" /&gt;&lt;/p&gt;
&lt;p&gt;People tend to talk in a little circle and then move around over
time. It's basically no different than a happy hour except it is over
a non-alcoholic drink and it's in the morning.&lt;/p&gt;
&lt;p&gt;All I have to do as the organizer is periodically tell people about
the &lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;Google Form&lt;/a&gt;
to fill out. I got people to sign up to the list by posting about this
on Twitter and LinkedIn. And then once a month I send an email bcc-ing
everyone on the list and ask them to respond for an invite.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/coffeeclub-invite.png" src="/assets/coffeeclub-invite.png" /&gt;&lt;/p&gt;
&lt;p&gt;The first 20 people to respond get a calendar invite.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/coffeeclub-invite.png" src="/assets/coffee-club-invite.png" /&gt;&lt;/p&gt;
&lt;p&gt;I mention all of this because people ask how they can start a coffee
club in their city. They ask how it works. But it's very simple! One
of the least-effortful ways to bring together people in your city.&lt;/p&gt;
&lt;p&gt;If your city does not have indoor public spaces, you could use a
food court, or a cafe, or a park during months where it is warm.&lt;/p&gt;
&lt;p&gt;For example, the &lt;a href="https://blinsay.com/chc3/"&gt;Cobble Hill Computer Coffee
Club&lt;/a&gt; is one that meets outdoors at a park.&lt;/p&gt;
&lt;p&gt;Good luck! :)&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;How I run a coffee club, a short guide for others who might be interested in running one. It's very simple!&lt;a href="https://t.co/UgRWDQOA3v"&gt;https://t.co/UgRWDQOA3v&lt;/a&gt; &lt;a href="https://t.co/5wYrLW7u6D"&gt;pic.twitter.com/5wYrLW7u6D&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1874213922271879650?ref_src=twsrc%5Etfw"&gt;December 31, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-12-31-how-i-run-a-coffee-club.html</guid></item><item><title>2024</title><link>https://b.yuxuan.org/2024</link><description>&lt;h2&gt;旅行&lt;/h2&gt;

&lt;figure&gt;
  &lt;img alt="2024 flight map" src="https://lh3.googleusercontent.com/pw/AP1GczMwrZIX0e-50-t2JQIb0NvNUdq85bpZulKeTqMMU4eIQLw6xH81Jw9-JgxF93YGtD-kmu3biR-3diZCFTaPrdoJXLPg5xcglV4hxS-JY4i3tv4eEtqSRF_lsgtHms6YRbIDKtTGBy2JBbBjfK_NDQ1PQQ=w720" /&gt;
  &lt;figcaption&gt;&lt;a href="https://photos.app.goo.gl/5t8WsPqx5ggHx1ak8"&gt;2024 flight map&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;今年因为种种原因飞了五个跨太平洋的往返，应该是我有史以来飞得最多的一年了。&lt;/p&gt;

&lt;p&gt;不过今年旅行最大的亮点是去阿拉斯加的 Katmai 国家公园看熊抓三文鱼吃。这个我单独写过&lt;a href="https://b.yuxuan.org/katmai-brooks-falls"&gt;一篇游记&lt;/a&gt;：&lt;/p&gt;

&lt;figure&gt;
  &lt;img alt="A bear standing up in the river with tongue out" src="https://lh3.googleusercontent.com/pw/AP1GczP7xcE6CoajaANDcOoQAuinPiuS1Szd33UAcQeBCZbjXH3XvOSDAeiNNs-iH-LudVMyMUcIzTyHPwrJTF5PS8zRdUI4o1kR9mWjPLJmGlPeh05R2x61tvSpAvFHrkN5cQMfgNc-EDj_STPe0f8m7WazRA=w720" /&gt;
  &lt;figcaption&gt;&lt;a href="https://photos.google.com/share/AF1QipOBUJm4P4r9ivzdzayLEXcJs6w9bCpiu2RbVDC04N1ysZPgroKwGX-NnYVvzjZ_UA/photo/AF1QipNvKITL1PvUmUrl_PLmozCvnWUXyo2w8edICZTU?key=ZmVZdXRwWXF0RGZERm1BRnFGc3hKbHFTXzRNS3BB"&gt;A bear standing up in the river with tongue out&lt;/a&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2&gt;影视&lt;/h2&gt;

&lt;p&gt;今年看了五十几部电影，因为长途飞得多有挺大比例是在飞机上看的。印象比较深的几部是：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.imdb.com/title/tt28151876/"&gt;热辣滚烫&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.imdb.com/title/tt28106766/"&gt;走私&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.imdb.com/title/tt17009710/"&gt;Anatomie d’une chute (坠落的审判)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.imdb.com/title/tt17279496/"&gt;Civil War&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.imdb.com/title/tt17079606/"&gt;鬼才之道&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.imdb.com/title/tt31807233/"&gt;好东西&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;算是华语片的大年了。&lt;/p&gt;

&lt;h2&gt;娱乐&lt;/h2&gt;

&lt;p&gt;今年继续没有玩几个游戏。到了年底觉得印象深刻值得一说的就是 &lt;a href="https://neodb.social/game/6K8oUdvuUpJJcPj1jR6a9R"&gt;Animal Well&lt;/a&gt;, &lt;a href="https://neodb.social/game/54AitEewC9jaBcgyeFOPua"&gt;Astro Bot&lt;/a&gt;, 和 &lt;a href="https://neodb.social/game/3ptorNahFU4Dd11d8584ga"&gt;Balatro&lt;/a&gt; 这三个。&lt;/p&gt;

&lt;p&gt;桌游方面今年主要就是在玩 &lt;a href="https://boardgamegeek.com/boardgame/338960/slay-the-spire-the-board-game"&gt;Slay the Spire 的桌游版&lt;/a&gt;，跟同伴一起已经通关两次了。&lt;/p&gt;

&lt;p&gt;今年开始从图书馆借电子书在 Kobo 上读，目前读了三本。&lt;/p&gt;</description><author>La Vita è Bear</author><pubDate>Tue, 31 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://b.yuxuan.org/2024</guid></item><item><title>New Year, New Logo</title><link>https://tuckersiemens.com/posts/new-year-new-logo/</link><description>&lt;p&gt;I've wanted a more recognizable online "brand" for years. I took a stab at
creating something myself a few times, but was never satisfied with what I
came up with so I finally asked a friend to design a logo for me. The process
was delightful and I'm very happy with the results.&lt;/p&gt;</description><author>Reilly Tucker Siemens</author><pubDate>Tue, 31 Dec 2024 01:37:07 GMT</pubDate><guid isPermaLink="true">https://tuckersiemens.com/posts/new-year-new-logo/</guid></item><item><title>The CP/M emulator runs on Windows, maybe!</title><link>https://blog.steve.fi/the_cp_m_emulator_runs_on_windows__maybe_.html</link><description>&lt;p&gt;Today I made a new release of my &lt;a href="https://github.com/skx/cpmulator" rel="nofollow"&gt;CP/M emulator&lt;/a&gt; and I think that maybe now it will run on Microsoft Windows.  Unfortunately I cannot test it!&lt;/p&gt;

&lt;p&gt;A working CP/M implementation needs to provide facilities for reading input from the console, both reading a complete line of text and individual keystrokes.  These input functions need to handle several different types of input:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Blocking, waiting for input to become available.&lt;/li&gt;
&lt;li&gt;Non-blocking, returning any pending input if it is available otherwise nothing.&lt;/li&gt;
&lt;li&gt;With echo, so the user can see what they typed.&lt;/li&gt;
&lt;li&gt;Without echo, so the keys are returned by not displayed ot the user.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the past we used a Unix-specific approach to handle the enabling and disabling of keyboard echoing (specifically we executed the &lt;code&gt;stty&lt;/code&gt; binary to enable/disable echos), but this release adds a more portable solution, based around &lt;a href="https://github.com/nsf/termbox-go" rel="nofollow"&gt;termbox-go&lt;/a&gt; which is the new default, and should allow our emulator to work on Microsoft Windows systems.&lt;/p&gt;

&lt;p&gt;We always had the ability to select between a number of different &lt;em&gt;output&lt;/em&gt; drivers, and as of this release we can now select between multiple &lt;em&gt;input&lt;/em&gt; drivers too - with the new portable option being the default.  This has been tested on MacOS X systems, as well as GNU/Linux, but sadly I don't have access to Windows to test that.&lt;/p&gt;

&lt;p&gt;Fingers crossed it's all good now though, happy new year!&lt;/p&gt;</description><author>Steve Kemp's Blog</author><pubDate>Tue, 31 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steve.fi/the_cp_m_emulator_runs_on_windows__maybe_.html</guid></item><item><title>Culture talk: What I learned from building a distributed database that never took off</title><link>https://bytepawn.com/techtalk-scalien.html</link><description>&lt;p&gt;Recently, I delivered a culture talk on Scalien, my old database startup that never took off. I discussed the business, product and some technical challenges and lessons learned from developing ScalienDB, a Paxos-based distributed NoSQL database.&lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/scalien-com.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Tue, 31 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/techtalk-scalien.html</guid></item><item><title>Unclenching</title><link>https://www.aswathkrishnan.com/2024/12/unclenching.html</link><description>&lt;p&gt;We were bringing over a fragile gingerbread house, precariously placed on the floor of our car’s back row, to a Christmas party. With every stop, swerve, and bump, I felt an involuntary pang of worry. My body tensed, my thoughts raced, and I caught myself clenching, both mentally and physically.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;The gingerbread house arrived safely, and everyone loved it. But the ride wasn’t enjoyable for me. The concern and the clenching—it didn’t actually contribute to the house’s safe arrival. It just made the trip unpleasant.&lt;/p&gt;&lt;p&gt;Looking back, I realized this wasn’t just about a gingerbread house. This is how most of us go through life—clenched. We’re constantly on edge, worrying about what others think, stressing over unfinished tasks and goals, craving more, fearing failure, envious, judgmental, or bracing ourselves for what might go wrong. Even when things are fine, we’re often stuck in a low-grade state of tension.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;A lot of this &lt;a href="https://www.aswathkrishnan.com/2023/12/mental-knots-and-massaging-them.html" target="_blank"&gt;clenching is automatic and subconscious&lt;/a&gt;. It happens so fast, in microseconds, and so often that we hardly notice it. We just feel the aftereffects—tension in our necks, knots in our stomachs, a vague sense of unease. And while it might seem harmless, this constant clenching robs us of something crucial: presence and joy, almost like a blossoming lotus strangled by weeds.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;To be fair, it mattered to us and others that the gingerbread house arrived intact. But the clenching? It didn’t help. We’d already done everything we could—driving carefully, avoiding sharp turns. Beyond that, the clenching was just noise, an exhausting habit my brain didn’t know how to let go of.&lt;/p&gt;&lt;p&gt;The truth is, clenching rarely changes the outcome of a situation. It’s our mind’s way of trying to “do something” even when there’s nothing left to do. But instead of helping, it keeps us trapped in a loop of worry and tension.&lt;/p&gt;&lt;p&gt;The key is learning to notice the clenching in the first place. This is harder than it sounds. Most of us are so used to living in this state that it feels normal. We don’t just experience clenching; we &lt;em&gt;become&lt;/em&gt; it. We get caught up in our stress, fears, and frustrations, losing the ability to step back and see them for what they are.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Much of Buddhism, and various contemplative paths, revolve around recognizing and letting go of these clinging states. Meditation, mindfulness, and introspection help us spot the roots of tension and either let them go or do something useful about them. It’s about pausing long enough to notice: “Oh, I’m clenching right now.” That simple act of noticing—without judgment—can be a game-changer. When you shine a light on your tension, it starts to loosen its grip.&lt;/p&gt;&lt;p&gt;Unclenching doesn’t mean we stop caring or trying. It just means we approach life with less resistance and more acceptance. Peace isn’t about adding something new to your life; it’s about letting go of what’s weighing you down - unclenching. And joy is just peace in motion.&lt;/p&gt;&lt;p&gt;So, the next time you catch yourself bracing—your jaw tight, your mind racing, your stomach knotted—pause. Take a breath. Ask yourself: “Is this clenching helping me or telling me something important? Is there something more I can do about the situation” And if the answer is no, let it go.&lt;/p&gt;&lt;p&gt;Because life isn’t just about arriving in one piece. It’s about enjoying the ride.&lt;/p&gt;</description><author>Aswath Krishnan</author><pubDate>Mon, 30 Dec 2024 21:58:34 GMT</pubDate><guid isPermaLink="true">https://www.aswathkrishnan.com/2024/12/unclenching.html</guid></item><item><title>Adding Pinch-To-Zoom to the Chart</title><link>https://littlegreenviper.com/adding-pinch-to-zoom-to-the-chart/</link><description>In this exercise, we&amp;#8217;ll add a &amp;#8220;pinch-to-zoom-in/out&amp;#8221; feature to the chart. It will be very basic, but should work quite well. The Basics The zoom will be X-axis only. It doesn&amp;#8217;t actually make sense to zoom in on the Y-axis, especially since we have the ability to select a bar, and get the exact values ... &lt;p class="read-more-container"&gt;&lt;a class="read-more button" href="https://littlegreenviper.com/adding-pinch-to-zoom-to-the-chart/#more-7111" rel="noopener noreferrer" title="Adding Pinch-To-Zoom to the Chart"&gt;Read more&lt;/a&gt;&lt;/p&gt;</description><author>Little Green Viper</author><pubDate>Mon, 30 Dec 2024 21:56:12 GMT</pubDate><guid isPermaLink="true">https://littlegreenviper.com/adding-pinch-to-zoom-to-the-chart/</guid></item><item><title>AdditionalAuthorizationParameters in ASP.NET Core 9</title><link>https://nestenius.se/net/additionalauthorizationparameters-in-asp-net-core-9/</link><description>&lt;p&gt;In ASP.NET Core 9, a new feature called AdditionalAuthorizationParameters allows you to customize OAuth and OpenID Connect (OIDC) flows more quickly. This new feature allows developers to add custom authentication parameters without needing to rely on the complex workarounds that existed before ASP.NET Core 9 was released. Sounds familiar? Then you’re going to like this! [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/net/additionalauthorizationparameters-in-asp-net-core-9/"&gt;AdditionalAuthorizationParameters in ASP.NET Core 9&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Mon, 30 Dec 2024 18:42:55 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/net/additionalauthorizationparameters-in-asp-net-core-9/</guid></item><item><title>Amiga 500 Samsung Keyboard Repair</title><link>https://www.hackup.net/2024/12/amiga-500-samsung-keyboard-repair/</link><description>&lt;p&gt;Recently, an Amiga 500 in generally good condition found its way to me. It&amp;#8217;s my first Amiga ever, because back in the day I was loyal to Commodore&amp;#8217;s 8-bitters for a long time. So I missed the heyday of the &amp;#8230; &lt;a href="https://www.hackup.net/2024/12/amiga-500-samsung-keyboard-repair/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
The post &lt;a href="https://www.hackup.net/2024/12/amiga-500-samsung-keyboard-repair/"&gt;Amiga 500 Samsung Keyboard Repair&lt;/a&gt; first appeared on &lt;a href="https://www.hackup.net"&gt;hackup.net&lt;/a&gt;.</description><author>hackup.net</author><pubDate>Mon, 30 Dec 2024 18:14:07 GMT</pubDate><guid isPermaLink="true">https://www.hackup.net/2024/12/amiga-500-samsung-keyboard-repair/</guid></item><item><title>Review of 2024</title><link>https://www.extua.pw/blog/2024/12/31/review_of_2024/</link><description>I’m ending out this year on holiday in Brittany with Eileen and my family.</description><author>extua</author><pubDate>Mon, 30 Dec 2024 18:00:00 GMT</pubDate><guid isPermaLink="true">https://www.extua.pw/blog/2024/12/31/review_of_2024/</guid></item><item><title>Memories of Noah Gibbs</title><link>https://www.mgaudet.ca/blog/2024/12/30/memories-of-noah-gibbs</link><description>&lt;p class=""&gt;I was shocked to discover last week that &lt;a href="https://codefol.io"&gt;Noah Gibbs&lt;/a&gt; &lt;a href="https://archive.ph/MnsWi"&gt;had passed away&lt;/a&gt;. &lt;/p&gt;&lt;p class=""&gt;Memories are being collected for his family &lt;a href="https://forms.gle/zxzsXkSdun1ahSqE6"&gt;here&lt;/a&gt;. I thought I would share mine here as well. &lt;/p&gt;





















  
  



&lt;figure class="block-animation-none"&gt;
  &lt;blockquote&gt;
    &lt;span&gt;“&lt;/span&gt;My recollection of Noah is of a kind generous man who contributed tirelessly to the things he believed in. &lt;br /&gt;&lt;br /&gt;I first encountered Noah after I gave a conference talk calling for better benchmarks in the Ruby community. At the time I was working on a doomed Ruby JIT compiler. &lt;br /&gt;&lt;br /&gt;After that talk, Noah would go on the build Rails Ruby Bench. We corresponded a bit about it, and I started keeping track of his writing on the internet. &lt;br /&gt;&lt;br /&gt;Eventually I moved on from Ruby. Still, I kept track of Noah, and would read his writing when I came across it. He clearly was someone who thought deeply about the nature of building software, and what it was to be employed in this industry that demanded production from something that from the inside felt more like craft. &lt;br /&gt;&lt;br /&gt;You could see his commitment to community and mentorship in the things he wrote about, the podcasts he took, etc. &lt;br /&gt;&lt;br /&gt;Recently I had occasion to reach out to Noah again. My email started “Hey Noah,  I don’t know if you remember me” — I was truly gratified that his reply started “Hiya, Matt! Yes, I remember you. :-)” — then he generously answered my questions, fully in the spirit intended.&lt;br /&gt;&lt;br /&gt;I had always hoped to run into Noah again, to cross paths with him. I am so incredibly sorry for your loss. I hope this message helps in the tiniest bit to illuminate more of the ways in which Noah touched people, all over the world. &lt;br /&gt;&lt;span&gt;”&lt;/span&gt;
  &lt;/blockquote&gt;
  &lt;figcaption class="source"&gt;&amp;mdash; Matthew Gaudet, Edmonton, Alberta&lt;/figcaption&gt;
  
  
&lt;/figure&gt;


  &lt;p class=""&gt;Noah had a broad reach, but if anyone reading this has their own recollections, please send them along to his family. &lt;/p&gt;</description><author>Matthew Gaudet</author><pubDate>Mon, 30 Dec 2024 17:11:01 GMT</pubDate><guid isPermaLink="true">https://www.mgaudet.ca/blog/2024/12/30/memories-of-noah-gibbs</guid></item><item><title>How to Get Package Updates in Hard-to-Reach Places With RenovateBot</title><link>https://seankilleen.com/2024/12/how-to-get-package-updates-in-hard-to-reach-places-with-renovatebot/</link><description>No dependency left behind.</description><author>SeanKilleen.com</author><pubDate>Mon, 30 Dec 2024 16:00:00 GMT</pubDate><guid isPermaLink="true">https://seankilleen.com/2024/12/how-to-get-package-updates-in-hard-to-reach-places-with-renovatebot/</guid></item><item><title>2024 in review</title><link>https://callmeo.live/blog/2024-in-review/</link><description>&lt;p&gt;2024 felt more like a proper year &lt;a href="../2023-in-review/"&gt;than the last&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Amol Rajan casually dropped the &lt;a href="../../resources/vid/blog/2024/12/weneedjungle.mp4"&gt;sickest sample known to man&lt;/a&gt;, internet rapper Viper got arrested for kidnapping, then an ITV drama brought the Post Office Horizon scandal to the front of public consciousness making us all wish there were TV dramas about everything else wrong with the country.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.bbc.co.uk/news/uk-scotland-glasgow-west-68431728"&gt;Willy&amp;rsquo;s Chocolate Experience&lt;/a&gt; took the world by storm with how hilariously abysmal it was. It taught people to avoid AI-Generated rubbish until &lt;a href="https://www.euronews.com/culture/2024/11/01/thousands-go-to-fake-ai-invented-dublin-halloween-parade"&gt;they didn&amp;rsquo;t&lt;/a&gt;. &lt;em&gt;Formerly Twitter&lt;/em&gt; went wild over a 20-year old &lt;a href="https://youtu.be/rvI1bNfleFs?t=20"&gt;advertising gimmick&lt;/a&gt; and for a fortnight every video became an ode to a Chilean beer.&lt;/p&gt;
&lt;p&gt;Lost Media fans rejoiced as &lt;em&gt;Ulterior Motives&lt;/em&gt; was finally discovered in a&amp;hellip; Uh. &lt;a href="https://www.rollingstone.com/culture/culture-features/musicians-ulterior-motives-lost-media-track-80s-album-1235012719/"&gt;Awkward&lt;/a&gt;. On top of that, &lt;em&gt;The Most Mysterious Song on the Internet&lt;/em&gt; was &lt;a href="https://www.cbsnews.com/news/mystery-song-identified-subways-of-your-mind-fex/"&gt;finally identified&lt;/a&gt;, as well as a sodding &lt;a href="https://www.youtube.com/watch?v=YMMzPioQvOU"&gt;Mozart piece&lt;/a&gt; of all things.&lt;/p&gt;
&lt;p&gt;Also the Northern Lights &lt;a href="../sometimes-life-is-good/"&gt;went wild&lt;/a&gt; in May.&lt;/p&gt;
&lt;p&gt;At the halfway point, our Prime Minister called a General Election and bottled it, &lt;a href="https://www.theguardian.com/games/article/2024/jul/04/kein-the-most-delayed-video-game-in-history-released-after-22-years"&gt;Kien&lt;/a&gt; released for the Gameboy Advance, and Cypress Hill finally played with the London Symphony Orchestra. The &lt;a href="https://en.wikipedia.org/wiki/2024_CrowdStrike-related_IT_outages"&gt;Crowdstrike failure&lt;/a&gt; caused a right kerfuffle and brought a chunk of the world to a standstill, and Donald Trump got an ear piercing. 💅&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t really have notes for the rest of the year. Concord released and immediately &lt;a href="https://www.theguardian.com/games/article/2024/sep/11/sonys-hero-shooter-concord-failed-spectacularly-heres-where-it-went-wrong"&gt;shut down&lt;/a&gt;, a capybara &lt;a href="https://www.bbc.co.uk/news/articles/c3wp13vqpl8o"&gt;escaped a zoo&lt;/a&gt;, Luigi became Nintendo&amp;rsquo;s most popular character somehow, and there was a new Wallace and Gromit film. That about does it.&lt;/p&gt;
&lt;p&gt;Oh, and the felled Sycamore Gap tree &lt;a href="https://www.bbc.com/news/science-environment-68497720"&gt;shows signs of life&lt;/a&gt;. Things can&amp;rsquo;t be that bad.&lt;/p&gt;
&lt;p&gt;In terms of &lt;em&gt;web stuff&lt;/em&gt;, there have been better years. Big Tech is still hellbent on enshittification, anything to make a quick buck. &lt;a href="https://en.wikipedia.org/wiki/AI_slop"&gt;AI Slop&lt;/a&gt; is inescapable now and has &lt;a href="https://petapixel.com/2024/10/10/ai-is-muddying-google-image-search-results-people-arent-happy/"&gt;ruined&lt;/a&gt; image search. Google are still fighting adblockers, Mozilla is now into advertising, and even NaNoWriMo has jumped on the generative AI bandwagon for some reason.&lt;/p&gt;
&lt;p&gt;But despite all of this, it&amp;rsquo;s worth mentioning that people want the online world to be better.&lt;/p&gt;
&lt;p&gt;Cohost unfortunately &lt;a href="https://cohost.org/staff/post/7611443-cohost-to-shut-down"&gt;shut down&lt;/a&gt;, but it shown us all that social media can be better for everyone. The indie web is still thriving, community wikis are &lt;a href="https://weirdgloop.org/blog/why-were-helping-more-wikis-move-away-from-fandom"&gt;clawing back&lt;/a&gt; at the predatory grip of Fandom, and in response to X going to hell there&amp;rsquo;s been a mass exodus to Bluesky – whilst I doubt their decentralisation plans and don&amp;rsquo;t see anything stopping it from becoming Twitter 2.0, it&amp;rsquo;s certainly a nicer place.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="the-stats"&gt;The stats&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;6 blog posts&lt;/li&gt;
&lt;li&gt;11 books read
&lt;ul&gt;
&lt;li&gt;Favourite: The Disaster Artist&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;17 Films watched
&lt;ul&gt;
&lt;li&gt;Favourite: Eyes Wide Shut&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;9 TV shows watched
&lt;ul&gt;
&lt;li&gt;Favourite: Ashes to Ashes&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;13 Games played
&lt;ul&gt;
&lt;li&gt;Favourite: Disco Elysium&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Favourite song I listened to: &lt;a href="https://www.youtube.com/watch?v=xUH7nuWuPdE"&gt;Tiger King&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;8 games I bought on sale then never played&lt;/li&gt;
&lt;li&gt;2 hours spent lying on the floor listening to Ultravox&amp;rsquo;s &lt;a href="https://www.youtube.com/watch?v=xJeWySiuq1I"&gt;&lt;em&gt;Vienna&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;1 time my gay cat caught something&lt;/li&gt;
&lt;li&gt;7½ hours spent waiting in A&amp;amp;E only for everything to be okay&lt;/li&gt;
&lt;li&gt;Word of the year: Desultory&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="blog-posts-wot-didnt-make-the-cut"&gt;Blog posts wot didn’t make the cut&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Fiction and Vulnerability&lt;/strong&gt; – I&amp;rsquo;m not a writer, but I&amp;rsquo;d occasionally like to try it. Writing fiction and presenting it to the world (even as a trivial challenge for this blog) feels way more daunting &amp;amp; vulnerable than writing about childhood bullying.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The 2024 Election&lt;/strong&gt; – The Conservatives had nothing to show but decline after 14 years in power and Labour was the only real alternative. Simple as.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;What&amp;rsquo;s up with Skeletons?&lt;/strong&gt; – Exploring why you can put skeletons into any situation without them being out of place. I was hoping to release it on Halloween but I got preoccupied with &lt;a href="../blog/blog-update-pains/"&gt;porting my blog to Hugo&lt;/a&gt; and&amp;hellip; Meh.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="recommended-reads-from-2024"&gt;Recommended reads from 2024&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://injuly.in/blog/darker-internet/index.html"&gt;The internet is slipping out of our reach&lt;/a&gt; - InJuly&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stevenscrawls.com/delusional-desires/"&gt;Maybe your desires are delusional&lt;/a&gt; - Stephen Scrawls&lt;/li&gt;
&lt;li&gt;&lt;a href="https://josh.works/bollards"&gt;Bollards: Why &amp;amp; What&lt;/a&gt; - Josh Thompson&lt;/li&gt;
&lt;li&gt;&lt;a href="https://maxread.substack.com/p/my-kindle-thinks-im-stupid-now"&gt;My Kindle thinks I&amp;rsquo;m stupid now&lt;/a&gt; - Max Read&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pikuma.com/blog/jungle-music-video-game-drum-bass"&gt;The Impact of Jungle Music in 90s Video Game Development&lt;/a&gt; - Gustavo Pezzi, &lt;em&gt;Pikuma&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ontestautomation.com/i-am-tired-of-ai/"&gt;I am tired of AI&lt;/a&gt; - Bas Dijkstra&lt;/li&gt;
&lt;li&gt;&lt;a href="https://beeps.website/blog/2024-10-09-why-govuk-exit-this-page-doesnt-use-escape/"&gt;Why GOV.UK’s Exit this Page component doesn’t use the Escape key&lt;/a&gt; - Beeps&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jan.miksovsky.com/posts/2024/11-12-momboard"&gt;MomBoard: E-ink display for a parent with amnesia&lt;/a&gt; - Jan Miksovsky&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scottrichmond.me/the-web-is-too-big/"&gt;The web is too big, or scaling down&lt;/a&gt; - Scott C. Richmond&lt;/li&gt;
&lt;li&gt;&lt;a href="https://archive.is/6a6XJ"&gt;Test&lt;/a&gt; - U.S. Department of Defense&lt;/li&gt;
&lt;li&gt;&lt;a href="https://interconnected.org/home/2024/12/23/jailbreaking"&gt;Narrative jailbreaking for fun and profit&lt;/a&gt; - Matt Web&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Right, that&amp;rsquo;s 2024 done with.&lt;/p&gt;
&lt;p&gt;For me it&amp;rsquo;s been an eventful &lt;em&gt;non eventful&lt;/em&gt; year. I finally quit a job I was too scared to leave, but familial commitments have left me in an odd position for the time being. While I don&amp;rsquo;t have the highest hopes for 2025, if I play my cards right it might  be the start of a transitional period for my life. Gotta have hope innit.&lt;/p&gt;
&lt;p&gt;While I have faltered, this has been the year where I&amp;rsquo;ve accepted that I should try to be better. I&amp;rsquo;ve accepted that I can&amp;rsquo;t give up on my health. I still get overwhelmed by my thoughts and wish I could just give up, but there&amp;rsquo;s &lt;em&gt;something&lt;/em&gt; inside of me worth caring for. Also it turns out that yes, I can lose weight&amp;hellip; Just ignore what I put back on over Christmas.&lt;/p&gt;
&lt;p&gt;Is there anything I wish for in the future? &lt;del&gt;For things to stop being so annoying&lt;/del&gt; Temperance and fortitude ought to be the big one, building up mental endurance would help a lot with navigating life. Add in some self-love, stop the self-deprecation and hey, maybe I can feel great about myself.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll need help in figuring out things about who I really am, and I&amp;rsquo;ll need to steady myself to face it all. It&amp;rsquo;s big, it&amp;rsquo;s scary, but I think I can take on another year.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to thank my friends for being there for me this year – you know who you are. Thank you for the comfort through the low periods, and laughs through the highs. You&amp;rsquo;ve managed to keep me sane throughout all of this.&lt;/p&gt;
&lt;p&gt;Please be kind to yourself xx&lt;/p&gt;
&lt;p&gt;Olive&lt;/p&gt;</description><author>callmeolive</author><pubDate>Mon, 30 Dec 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">https://callmeo.live/blog/2024-in-review/</guid></item><item><title>Love for the WDC 65C02</title><link>https://www.masswerk.at/nowgobang/2024/love-for-the-wdc-65c02</link><description>WDC and Rockwell related additions to the 6502 instruction set sheet.</description><author>mass:werk – Now Go Bang!</author><pubDate>Mon, 30 Dec 2024 13:10:00 GMT</pubDate><guid isPermaLink="true">https://www.masswerk.at/nowgobang/2024/love-for-the-wdc-65c02</guid></item><item><title>The Power of Now by Eckhart Tolle</title><link>https://apurva-shukla.me/bookshelf/the-power-of-now/</link><description>⭐ ⭐ ⭐ ⭐ ⭐ The Power of Now was a deeply insightful read, that I got a lot out of. Flipping through it’s pages, the act of reading itself…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Mon, 30 Dec 2024 11:31:39 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/the-power-of-now/</guid></item><item><title>The Housemaid by Freida McFadden</title><link>https://apurva-shukla.me/bookshelf/the-housemaid/</link><description>⭐ ⭐ ⭐ ⭐ The Housemaid was a thriller in every sense of the word. I haven’t really experienced many books like this, where the pages seemed…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Mon, 30 Dec 2024 11:29:02 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/the-housemaid/</guid></item><item><title>Tomorrow, and Tomorrow, and Tomorrow by Gabrielle Zevin</title><link>https://apurva-shukla.me/bookshelf/tomorrow-and-tomorrow-and-tomorrow/</link><description>⭐ ⭐ ⭐ ⭐ Tomorrow and Tomorrow and Tomorrow is a read that attempts to span a lifetime.

It begins with Sam, crippled at a young age…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Mon, 30 Dec 2024 11:25:45 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/tomorrow-and-tomorrow-and-tomorrow/</guid></item><item><title>The spiritual and religious background of a childhood</title><link>https://jasononeil.au/2024/12/30/the-spiritual-and-religious-background-of-a-childhood/</link><description>Every year Jesus’ parents went to Jerusalem for the Passover festival. Luke 2:41 (NLT) My favourite podcast for a decade now has been On Being with Krista Tippett, and she opens every interview with the same question: &amp;#8220;Can you describe the spiritual or religious background of your childhood? (In whatever way you interpret that question).&amp;#8221; [&amp;#8230;]</description><author>Jason O'Neil</author><pubDate>Mon, 30 Dec 2024 11:20:35 GMT</pubDate><guid isPermaLink="true">https://jasononeil.au/2024/12/30/the-spiritual-and-religious-background-of-a-childhood/</guid></item><item><title>Attention pendulum (goal setting)</title><link>https://bill.harding.blog/2024/12/30/attention-pendulum-goal-setting/</link><description/><author>Relentless Simplicity</author><pubDate>Mon, 30 Dec 2024 02:51:35 GMT</pubDate><guid isPermaLink="true">https://bill.harding.blog/2024/12/30/attention-pendulum-goal-setting/</guid></item><item><title>The Nada 65 Panda: A QMK Success Story</title><link>https://thoughts.greyh.at/posts/nada-65-panda/</link><description>The Cerakey Nada 65 Panda is the world&amp;rsquo;s first keyboard featuring ceramic keycaps, designed to deliver the smoothest and thockiest typing possible. When I first heard about it, I knew I had to try it - the promise of ceramic keycaps was too intriguing to pass up. The keyboard arrived in beautiful packaging, and the build quality exceeded my expectations.</description><author>Terminal Thoughts</author><pubDate>Mon, 30 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://thoughts.greyh.at/posts/nada-65-panda/</guid></item><item><title>Books read in 2023</title><link>https://faingezicht.com/articles/2024/12/30/books-23/</link><description>Still trying to catch up on my lack of writing this year, here's the long awaited list of the books I read in 2023. Less than I wanted, but I'm happy with the mix. On top of a few great fiction books, I read books spanning classics, history, and economics. Unintentionally, I also ended up reading a bunch of memoirs.

Some recurring themes were the exploration of memory and identity, the nature of reality, and the consequences of technological change.

Beside the books below, I also read some technical books to sharpen my skills as I picked a stack for InScope's MVP. Those included William S. Vincent's _Django for Professionals_ and Miguel Grinberg's _React Mega-Tutorial_ and _SQLAlchemy in Practice_ (keeping that monolithic stack on Postgres a while longer would have been a better idea, but that's beside the point of this blog post). I also read parts of _Designing Autonomous AI_ by Kence Anderson while doing the Coursera &lt;a href="https://www.coursera.org/learn/dmrol"&gt;Decision Making and Reinforcement Learning&lt;/a&gt; class, and on the more managerial side also _Crucial Conversations_ by Patterson, Grenny, McMillan, and Switzler and _The Founder's Dilemmas_ by Noam Wasserman. 

&lt;div class="book-review"&gt;
  &lt;h4&gt;The Book of the New Sun - Gene Wolfe&lt;/h4&gt;
  &lt;p&gt;I started the year with the first two volumes of a book that had been highly recommended but which I ended up really disliking. Set in a far future where medieval aesthetics coexist with advanced technology, The Book of the New Sun by Gene Wolfe follows Severian, an apprentice torturer who is exiled for showing mercy to a prisoner. The story is told from Severian's perspective, challenging the reader with his unreliable narration. Before his expulsion, Severian had acted as a obedient cog in the bureaucratic machinery of The Autarchy, the all powerful authoritarian regime in his society, and the book deals with his reckoning of his new role as a lower level traveling brute. The world building is rich, but the plot is slow and I found the writing style and language to be unnecessarily pretentious. To boot, many of the plot lines were overt allegories of Christian theology, which I didn't care for. With its focus on semiotics, the nature of knowledge, and bend towards medieval history, at times I wished I had just been reading Umberto Eco's fiction instead. I do not plan to finish the series.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Skin in the Game - Nassim Nicholas Taleb&lt;/h4&gt;
  &lt;p&gt;This is the fourth book I read from Nassim Nicholas Taleb's Incerto series, missing only his book of aphorisms, The Bed of Procrustes . In his own words, Skin in the Game is a book about: a) uncertainty and the reliability of knowledge b) symmetry in human affairs, that is, fairness, justice, responsibility, and reciprocity c) information sharing in transactions d) rationality in complex systems and in the real world Also in his own words, "the entire point of the book is that in the real world it is hard to disentangle ethics on one hand from knowledge and competence on the other." This focus on ethical decision making comes down to accountability by having personal stakes in the outcome of the decisions one makes on behalf of others – skin in the game. It is really aligned with my libertarian leaning worldview. Making decisions for ourselves is already hard, so we should be extra careful when making decisions for others whose reality we can't possibly understand as well as our own. Taleb argues that people make better, more cautious, and more ethical decisions when they personally stand to lose something from the outcomes, preventing reckless risk taking. Without skin in the game, decision makers (think policymakers, corporate executives, or bureaucracies) end up reaping the benefits if things go well while transferring risks to others (taxpayers, employees, customers) when they don't. This is a clear reason to build institutions with aligned incentives and build natural checks against the unintended consequences of harmful decisions. The book is an explicit critique of crony capitalism and bad incentive structures in every day life. The world is too complex to be explained by sound bites and top down solutions. Taleb's writing style in this book is just as arrogant and condescending as in previous works of his I had read, but his ideas are thought provoking and, I tend to think, correct, so it is worth putting up with his tone.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Call Center Optimization - Ger Koole&lt;/h4&gt;
  &lt;p&gt;I read this book when I was exploring an idea in the revenue operations space, just before deciding to start InScope. The insight I was trying to apply came from a problem I had seen at Vouch, where complex sales required many cross team handoffs. As in other businesses, managers often didn't have visibility outside their silo, leading to local optimization. The theory of constraints tells us that businesses waste resources improving anything but the bottleneck, losing revenue and increasing costs. I wanted to apply this to staffing and prioritization in complex sales teams, but I needed to get a refresher on queueing theory first. This book was a suggestion from my Kellogg ops professor, Martin Lariviere. My idea was simple: to automatically map entire sales motions using data that companies are already collecting (in Salesforce, Zendesk, etc), and then to use that data to optimize the sales process. Analyzing the pipeline end to end could allow the team to identify the limiting factors keeping deals from closing. This would mean focusing on the most impactful changes or new hires to make, rather than the most visible problems or the loudest executive's views. Koole's book is a comprehensive guide to the mathematical modeling of call centers, and those models are analogous to other operations management problems. It covers the basics of queueing theory, Erlang formulas, and the mathematics of staffing and scheduling. I found it useful to brush up on the math, re learn the terminology, and to get a sense of the complexity of the problem. It was a good starting point for a project I didn't start.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Confessions - Augustine&lt;/h4&gt;
  &lt;p&gt;I read Augustine as part of a Catherine Project class, motivated by Martin Hägglund’s repeated references to it in This Life . Confessions is Augustine’s autobiographical account of his youth and conversion to Christianity, exploring themes of personal responsibility, free will, and sin. It’s a foundational text for Christianity, with enduring relevance in Western thought. I latched on to Augustine’s focus on language, and what he calls "the semantic problem," our struggle to communicate a shared reality. The book begins with his reflections on learning to speak as a child, distinguishing learning by study from learning by experience—a concept that feels surprisingly modern. His introspection on this topic, and especially Book X’s discussion of memory, imagination, and free will, reads almost like Wittgenstein or Douglas Hofstadter. The book has many emotional scenes of grief and family drama. One poignant scene recounts Augustine’s encounter with a drunken beggar in Milan, contrasting his own anxiety driven ambitions with the beggar’s fleeting joy. This moment encapsulates Augustine’s struggle between temporal pleasures and eternal joy. Like the strikingly modern discussion on language I mentioned earlier, this passage describes an experience that someone like me could easily have walking the streets of San Francisco in the 2020s. As a non religious reader, I found myself rolling my eyes often. Despite its deep insights, Confessions offers incomplete answers, making it a thought provoking but unresolved meditation on human existence. Find my more detailed, more meandering review here.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;The Great Gatsby - F. Scott Fitzgerald&lt;/h4&gt;
  &lt;p&gt;When talking about classic literature, my friends are always surprised I never read any of the books they were forced to read in high school. Growing up in Costa Rica, the canon I was exposed to barely included any US authors (for some reason the only one I recall having to read is Poe). My knowledge of US classics is pretty limited, so when Hannah suggested that we read The Great Gatsby together, I was excited to finally get to it. Set in the Roaring Twenties, it is a classic rags to riches novel about love, wealth, and to some extent a critique of the American Dream. The novel shows how the relentless pursuit of wealth and status erodes authenticity and meaning, reducing everything to a spectacle. The story revolves around new and old money, and the upper class' hope for a permanent spot in society contrasted with the promise of upward mobility that keeps the lower class engaged in the system. Crime, corruption, and sex were all much more present than I'd have expected for a novel written a hundred years ago and prescribed in the average US high school curriculum. It was a fun read, and I'm glad that I now understand the shared references to Gatsby that pop up every once in a while, but I expected it'd be more profound given its popularity.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;La Hojarasca - Gabriel García Márquez&lt;/h4&gt;
  &lt;p&gt;I picked up La Hojarasca ( Leaf Storm ) during my trip to Colombia. It is the short novel where García Márquez introduced his fictional town of Macondo, which would later serve as the setting for One Hundred Years of Solitude . Like in his other works, García Márquez begins with a dramatic scene and ties the reader in knots through the complexities of his layered narration. The book tells the story of a reclusive doctor who commits suicide, leaving his burial to a contentious promise upheld by one of the main characters, the aging Colonel. The doctor’s isolation —rooted in his refusal to treat wounded soldiers and his questionable relationship with his indigenous housemaid— earned him the town’s scorn before his death, forcing the colonel into a moral struggle on his behalf. As the narrative unravels the doctor’s mysterious past, it highlights the intersecting burdens of memory, war, and loyalty in a decaying Macondo. The book’s brevity makes it easier to hold on to the details and characters and to fully appreciate its complexity —something I struggled with in One Hundred Years of Solitude . Yet, despite its concise form, the novella is a powerful exploration of history’s impact and the ways unresolved conflicts and buried truths shape the lives of individuals and communities alike. The title refers to the literal litter accumulated in the town —most vividly in the doctror's abandoned house— but also metaphorically in the unresolved conflicts and tensions that haunt Macondo’s inhabitants. This "leaf storm" operates on both personal and political levels, from interpersonal betrayals to the larger upheaval brought by the arrival of the banana company, whose capitalist exploitation is hinted at through broader themes of historical and social decay. That last topic, unfortunately, was barely touched upon in the book, so it left me eager to explore more of Márquez's work.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Acid for the Children - Flea&lt;/h4&gt;
  &lt;p&gt;I've been a huge Red Hot Chili Peppers fan forever. Their 1999 album Californication was one of the first records I consciously chose to buy, and I've been listening to them since. Acid for the Children is Flea's (the band's bassist's) autobiography. I got to watch them at Bottlerock in Napa, which inspired me to listen to the audiobook, narrated by Flea himself, and really enjoyed it. Acid for the Children is a coming of age story that follows Flea from his "normal" childhood in New York City, and splitting time in Australia after his parents' divorce, to the harsh transition to his wild teenage years in Los Angeles after his mom moves the family there. In LA, he gets interested in jazz through his stepfather's influence and picks up the trumpet. Attending high school there, he meets the friends with whom he'd later form RHCP, and is introduced to the chaotic world of punk rock, and the drugs and mayhem of the LA scene. The book is raw, filled with stories of his struggles with his identity, addiction, poverty, and loss. Describing the violence at home and the chaos of his life skipping school and living on the streets, Flea writes with a sense of humor and self awareness that makes the book both heartbreaking and uplifting.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;The Storyteller - Dave Grohl&lt;/h4&gt;
  &lt;p&gt;The Storyteller is Dave Grohl's autobiography, which I also listened to as an audiobook narrated by the author. I was inspired to read it by a great show, too – the band headlined Outside Lands in '23, and it might be one of the best live performances I've ever seen. Whereas Flea's book focuses on his life pre RHCP, The Storyteller spans the milestones of Grohl's career and what came after success. It almost comes off as bragging. While it's mostly positive and inspiring, loss is also a recurring theme throughout the book. There's Kurt Cobain's suicide, the obvious instance any reader expects, but his childhood friend's Jimmy Swanson's death weighs even heavier than Cobain's, at least in writing. Grohl's book is very family oriented, crediting his mother for his early interest in music, while critiquing his dad for his lack of support for his musical ambitions. He describes getting into punk through his "cousin" in Chicago during a summer road trip, and how that shaped his musical tastes and his career. The book chronicles his rise to becoming rock royalty, climbing the ranks from the DC punk scene to Nirvana and then Foo Fighters, with surreal anecdotes about performing at the White House for Obama and his close friendship with Paul McCartney. The vignette about the Beatle teaching Grohl's daughter to play piano was a far cry from his punk days. I was not that into Foo Fighters until much later in life, but I'm glad I eventually found them. While Grohl's recent admission of infidelity violates a lot of the good boy persona he tries to construct in this book, I still think it is worth the time.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Slouching Towards Utopia - J. Bradford DeLong&lt;/h4&gt;
  &lt;p&gt;I've been reading DeLong's blog for 10+ years now, so I was excited to finally read one of his books with Slouching Towards Utopia . Blending economic history with incisive commentary on human aspiration and failure, DeLong creates a grand narrative of economic theory and ideology meeting the actual political actions of the last hundred years and change. DeLong clearly marks 1870 as the beginning of an unprecedented era of economic transformation and sustained economic growth, cataloguing key ideas, players, and events through the present. He calls this period "long twentieth century." Admitting the end of his named period is fuzzy – perhaps the 9/11 attacks, the '08 crisis, or even the rise of Trump or COVID – he goes with 2016 due to the regime change in the US, and leaves the question open for future scholars. His core argument is that this period saw a growing belief that humanity could solve political economic problems and escape the Malthusian trap by improving governance and using knowledge. This belief took many shapes, bearing fruit fueled by technology and globalization. However, the compounding effects of progress came with a century of dislocation. Noting that the rise of industrial capitalism happened in tandem with mass migration and new ideologies, the book repeatedly highlights how ideas shaped economic and political outcomes. DeLong explores the efficiency gained from markets, the evolution of their dynamics, and their discontents, from Hayek's celebration of decentralized problem solving and Schumpeter's "creative destruction" to Keynes' intermediated market system and Polanyi's double movement critique. He also discusses the social experiments in Europe and the parallel fascist embrace of nationalism and war, underscoring how beliefs — both utopian and dystopian — redirected historical trajectories. Focusing on the US and its leaders, DeLong argues that while their role was exceptional, many of the policies that they coalesced around came up in large part due to luck and path dependence — the contingency of who was in the seat at key moments. The book is both an optimistic testament to progress and a sobering reminder of its limits. The marriage of market efficiency and state intervention that the Allies embraced (and pushed) after WWII, as seen in mid century social democracies, worked temporarily but ultimately unraveled. Despite technological advancements that granted humanity "godlike powers," societal challenges — inequality, nationalism, and environmental crises — have proved stubborn. So DeLong asks why, with so much capacity, do we remain so far from utopia? He challenges readers to think critically about whether humanity’s slouch toward utopia is inevitable or if renewed efforts are required to build a world worthy of our technological and organizational capabilities. I am glad he is asking these questions, because too many people take progress for granted.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Grit - Angela Duckworth&lt;/h4&gt;
  &lt;p&gt;Grit explores the idea that passion and perseverance are more critical to success than innate talent, encapsulated in Duckworth's memorable "effort counts twice" formula. Through anecdotes ranging from her research on which West Point cadets drop out and which ones succeed to studying Olympic athletes' training regimes, Duckworth illustrates how sustained effort often outpaces raw ability in achieving long term goals. Her central thesis is compelling, and the blend of research and real world stories makes the book approachable, though it’s more inspirational than groundbreaking in its insights. That said, the book was pretty forgettable (something I can attest to a year and a half after listening to it). An hour long podcast interview or even her short TED talk would have sufficed for me, in retrospect. Grit is simply pop psychology, with the tone of a motivational airport read instead of the rigorous scientific exploration I was hoping for.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Tale of love and darkness - Amos Oz&lt;/h4&gt;
  &lt;p&gt;Until last year, Amos Oz was likely the author who had languished the longest in my to read list. After sitting in the queue for at least 15 (!) years, I had the (un)fortunate timing of deciding to read him during my trip to Israel a few months before the conflict exploded again. A Tale of Love and Darkness is Oz's memoir, covering his childhood in Jerusalem and his later life in Kibbutz Hulda, along with a very personal telling of his family's history. The book is a meditation on family, the nature of memory, and the ways in which we construct our own narratives. The book takes intimate moments like planting and keeping gardens with his dad or reliving the grief after his mom's suicide, and interweaves them with Oz's first person chronicle of the nascent state. It is a moving portrait of a family, a country, and a time. Even in English translation, you can feel the rebirth of the Hebrew language and the formation of Israeli culture through its pages, as Oz captures both the ancient roots and tells us of his family's involvement in the creation of the nation. The formation of the Israeli identity was not accidental, and you can see traces of it throughout the whole story. Much of the book deals with the War of 1948. While I have a lot to say about Oz's view on the Zionist movement of the 30s and the 40s, and its leaders, I will instead just reproduce a section that stuck with me, and probably struck me more than anything else in the book: I asked Ephraim if he had ever, in the War of Independence or during the troubles in the 1930s, shot and killed one of those murderers. I could not see Ephraim's face in the dark, but there was a certain subversive irony, a strange sarcastic sadness in his voice as he replied, after a short pensive silence: "Murderers? What d'you expect from them? From their point of view, we are aliens from outer space who have landed and trespassed on their land, gradually taken over parts of it, and while we promise them that we've come here to lavish all sorts of goodies on them—cure them of ringworm and trachoma, free them from backwardness, ignorance, and feudal oppression—we've craftily grabbed more and more of their land. Vell, what did you think? That they should thank us? That they should come out to greet us with drums and cymbals? That they should respectfully hand over the keys to the whole land just because our ancestors lived here once? Is it any wonder they've taken up arms against us? And now that we've inflicted a crushing defeat on them and hundreds of thousands of them are living in refugee camps—what, d'you expect them to celebrate with us and wish us luck?" I was shocked. Even though I had come a long way from the rhetoric of Herut and the Klausner family, I was still a conformist product of a Zionist upbringing. [...] "In that case, what are you doing here with your gun? Why don't you emigrate? Or take your gun and go and fight on their side?" I could hear his sad smile in the dark: "Their side? But their side doesn’t want me. Nowhere in the world wants me. Nobody in the world wants me. That’s the whole point. It seems there are too many of my kind in every country. That's the only reason I'm here. That's the only reason I'm carrying a gun, so they won't kick me out of here the way they kicked me out of everywhere else." Reading this passage today, in the context of current events, is particularly poignant. Oz managed to capture both the complexity and the tragedy of the situation in a way that feels as relevant now as it was then. The book is a masterpiece of memoir writing, and a much needed perspective of the complexity of humanity in a region where narratives are too often reduced to simplistic binaries.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Kitchen Confidential - Anthony Bourdain&lt;/h4&gt;
  &lt;p&gt;I was first drawn to Anthony Bourdain through his TV show Parts Unknown and picked up his memoir, Kitchen Confidential , after his suicide in 2018. The book, first published in 2000, offers irreverent glimpses into his early career —from dishwasher in Cape Cod to celebrity chef in NYC haute cuisine— largely crediting his peers rather than his formal education at CIA. It was published when celebrity chef culture was nascent, unveling gritty behind the scenes details of restaurant kitchens and received as an exposé. Although I hoped to get some cooking lessons out of it, the book focuses more on industry realities than culinary skills. The big reveal is how much butter and shallots are used in restaurants. Beyond the cooking tips, Bourdain candidly shares unsettling industry practices and personal struggles, like how high pressure environments fueled his drug issues. Despite these disclosures, his narrative is sharp, witty, and filled with a genuine passion for the craft. Some of the stories are surprisingly hilarious, and outright vulgar. Other highlights include his vivid recounts of travels to Japan and France, foreshadowing his future TV success. Kitchen Confidential is a great book for any foodie, especially one with a sense of humor.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;La Invención de Morel - Adolfo Bioy Casares&lt;/h4&gt;
  &lt;p&gt;Last year, I took a Catherine Project class on Bioy Casares, the first they offered in Spanish. I had been eager to explore Bioy Casares' work for years after reading Borges mentioning him and his work. La Invención de Morel ( The Invention of Morel ) is an early exercise in speculative fiction, blending philosophical inquiry with a narrative of obsession and isolation. The novel explores technology as a means to transcend mortality, dramatized through a fugitive narrator who stumbles upon a mysterious island with eerie, deserted structures and a group of odd tourists who seem to ignore him. He soon realizes these are not people but recordings replayed by Morel’s invention—a device that captures human essence in a perpetual, unaltered loop. Bioy Casares uses an unreliable narrator to emphasize the ambiguity of reality versus illusion, blurring distinctions between signifier and signified. Morel, both inventor and participant in his recordings, gives a speech to his guests' avatars which made me think of Cory Doctorow's take on mind uploading in Walkaway and Down and Out in the Magic Kingdom , particularly in relation to recent AI advancements. Unlike Morel's subjects' recurring lives, Doctorow’s approach is dynamic, where consciousness is uploaded to a digital medium, allowing characters and copies to exist as active players in virtual environments, offering an immortality that is interactive and evolving. We can trace a clear line from simple cameras and phonographs that motivated Bioy Casares in the 1930s to the early aughts' computers that inspired Doctorow, all the way through today's LLMs. The appearance of LLMs have begun to make the latter's "immortality" closer to reality, with "griefbots" simulating deceased loved ones by using data they left behind as input, for example. Both Bioy Casares and Doctorow explore the limits of technology to capture consciousness and the implications of extending existence through it, posing ethical questions about identity, continuity of self, and what it truly means to be alive. La Invención de Morel is a sharp commentary on the perils of mistaking images for reality, and reading nearly a century after its publication made this metaphysical puzzle on immortality feel remarkably prescient.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;El Sueño de los Héroes - Adolfo Bioy Casares&lt;/h4&gt;
  &lt;p&gt;Following La Invención de Morel , I read El Sueño de los Héroes ( The Dream of Heroes ), which I liked the least of the trio. The psychological thriller follows Emilio Gauna and friends as he tries to make sense of the disjointed events of a black out carnival night a few years prior. Gauna's recollections blend into a surreal mess, making the story a slow burn, with Gauna's life, memories, and dreams intertwining as he reconstructs the past. As the story unfolds, Gauna develops an ambiguous romantic relationship, and his friendship with people he previously admired unravels. Early in the book, Gauna meets Taboada, a seer who serves as a guide of sorts, nudging him toward revisiting the fateful carnival night. His insights and cryptic behavior suggest he understands more about Gauna’s destiny than he lets on, though. The novel builds a suspenseful recursive narrative where mundane choices ripple into monumental consequences, confronting the reader with a tension between free will and the deterministic pull of destiny. At its core, the novel is about memory and the deceptive nature of self perception. Themes of honor and masculinity thread Emilio’s journey, as he questions who his real friends are, and whether his actions are driven by his own choices. The novel's slow pace and intricate plot where dreams, fate, and reality overlap left me a bit lost. Perhaps the most interesting piece is the implicit contrast between the rich and educated with the poor as they navigate the neighborhoods of old Buenos Aires. The book is a challenging read, but some call it Bioy Casares' best work, so I might have missed some of the subtleties that make it a classic of Argentine literature.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Diario de la Guerra del Cerdo - Adolfo Bioy Casares&lt;/h4&gt;
  &lt;p&gt;The last book I read for the Bioy Casares class was Diario de la Guerra del Cerdo ( Diary of the War of the Pig ), a dark comedy about generational conflict where a mysterious political movement causes young people in Buenos Aires to attack and threaten the elderly without apparent reason. The narrative follows Isidoro Vidal, a retiree who considers himself between the two age groups, as he becomes embroiled in the chaos of the escalating violence. The novel reflects on the fear of aging and the value society places on youth. It also touches on the cyclical nature of life, as young characters grapple with the realization that they too will one day be old, and targeted by the same violence. Bioy Casares uses the purge as an allegory for broader social exclusion and violence, warning of how easily communities can scapegoat others under the right conditions. Unfortunately, no one in the class knew much Argentinian history, but we agreed that it was an allegory for real political tensions in Argentina during the late 1960s, where much like in the rest of the world at the time, the youth were rebelling against the establishment.&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Founder vs Investor - Elizabeth Joy Zalman and Jerry Neumann&lt;/h4&gt;
  &lt;p&gt;After following Neumann's blog Reaction Wheel for years, I felt lucky that I got to attend the Bloomberg Beta book launch event in SF to hear Neumann and Zalman discuss Founder vs Investor . The book is structured as a set of dialogues exploring the relationship between these two groups, clarifying when incentives are aligned and when they grow apart, and how the dynamics play out over time as companies grow and more investors get involved. Zalman takes the vantage point of a founder, a role she's successfuly been in multiple times, while Neumann sits across the table as her former investor, and a venture veteran. The conversations cover a wide range of topics, from general advice on fundraising, to specific deals the authors were involved in and how they were negotiated, how to choose investors (or pick founders), dealing with boards of directors, specific fundraise terms worth negotiating for, and more. Needless to say, the two authors don't always agree, which is half the fun. As Neumann says, understanding "how the person on the other side of the table thinks is more important, practically, than what you believe." The book is a good exercise in building empathy and driving clarity between two groups that are forced to work together, but whose members can't really understand each other. Now who's writing Founder vs. Founder?&lt;/p&gt;
&lt;/div&gt;

&lt;div class="book-review"&gt;
  &lt;h4&gt;Blindness - José Saramago &lt;small&gt;&lt;i&gt;(translated by Giovanni Pontiero)&lt;/i&gt;&lt;/small&gt;&lt;/h4&gt;
  &lt;p&gt;Blindness is an allegory of human fragility, community, and the veneer of civilization. Saramago’s story captures the chaos that unfolds when an epidemic of blindness suddenly hits an unnamed town, leaving its society grappling with the breakdown of their daily routines, norms, and eventually all the systems that held it together. The narrative, often brutal and always intimate, slowly ramps up from a single crime to full collective dehumanization as the disease spreads among unnamed characters. I was not particularly fond of Saramago's signature run on style, and the lack of punctuation made following some plot lines harder than necessary, but the book's themes and imagery make up for it in their powerful portrayal of human nature in the face of societal collapse. Existential inquiry resonates throughout. Saramago probes at how humanity frays when stripped of its structures and senses, with people in the story constantly questioning their inherited morality. At the same time, the characters try to remain "good," struggling to maintain their dignity through small, cleansing acts. The passages where the group discusses governance and the lines between justice and vengeance, or survival and self degradation, recurringly made me think of Holocaust stories, and how the people in real life are sometimes forced to make similar choices. Blindness is a masterclass dystopia, and a reminder of how fragile the social fabric we take for granted truly is.&lt;/p&gt;
&lt;/div&gt;

You can find my lists from previous years here: &lt;a href="/articles/2024/07/01/books/"&gt;2022&lt;/a&gt;, &lt;a href="/articles/2022/11/20/books/"&gt;2021&lt;/a&gt;, &lt;a href="/articles/2021/01/08/books/"&gt;2020&lt;/a&gt;, &lt;a href="/articles/2020/02/09/books/"&gt;2019&lt;/a&gt;, &lt;a href="/articles/2019/02/14/books/"&gt;2018&lt;/a&gt;, &lt;a href="/articles/2018/01/07/books/"&gt;2017&lt;/a&gt;, and &lt;a href="/articles/2017/01/06/books/"&gt;2016&lt;/a&gt;.

&lt;hr /&gt;

&lt;small&gt;&lt;em&gt;Photo: Pop-up books, by me. Previously posted on &lt;a href="/photos/2024/08/01/catch-up/"&gt;A year of memories, in film&lt;/a&gt;.&lt;/em&gt;&lt;/small&gt;</description><author>Avy Faingezicht</author><pubDate>Mon, 30 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://faingezicht.com/articles/2024/12/30/books-23/</guid></item><item><title>Confessions, a short review</title><link>https://faingezicht.com/articles/2024/12/30/confessions/</link><description>I read Augustine's _Confessions_ as part of a Catherine Project class. The book was repeatedly cited by Martin Hägglund in _This Life_, a book I had quite enjoyed reading &lt;a href="/articles/2021/01/08/books#this-life"&gt;a few years before&lt;/a&gt;, and he emphasized the nuance in understanding Augustine, so I chose to do a guided reading. 

_Confessions_ is an autobiographical work, recounting Augustine's youth and his conversion to Christianity. It is one of the foundational texts in Christianity, dealing with personal responsibility, free will, and sin. It's influence is clear in the the Western worldviews we inherited. The Chadwick translation's introduction comments on Augustine's fascination with words, and his attention to the trouble we have communicating with one another, which he calls _the semantic problem._ The book opens with a vignette about Augustine learning to speak as a child, and then on him learning Greek and Latin. I immediately latched onto this language theme, which echoes through the book. Communication requires, by definition, a shared understanding of reality. Religious texts, however, tend to point us in the opposite direction, assuming that without sharing a single objective truth or dogma there can't be real understanding. His argument distinguishing the mechanics of learning by studying from learning by doing or experiencing felt very modern. Throughout the book I was surprised by the deep similarity between his descriptions of his life, and how we experience ours 1600 years later.

As someone who's not religious and who has little depth in Christian ideas, I read through the lens of good/evil and right/wrong, more than sin specifically. Not coincidentally, Augustine often invokes &lt;a href="https://en.wikipedia.org/wiki/Manichaeism"&gt;Manichaeism&lt;/a&gt;, a philosophy centered on this duality, as a contrast to his position that both good and bad come from the same source. The reading group struggled with this distinction: Our acts bring about changes in the physical world, and since those can be harmful they can be evil. Maybe, doing nothing doesn't bring about change, so we can't be responsible for what we don't do? But then, are we responsible for not stopping things from happening? Is it a sin for a bystander to stop a group of teenagers from stealing some fruit, and taking away their pleasure, or is that a positive for society? An objective morality seems prerequisite to this discussion, but Augustine just assumes it.

At times, especially when discussing memory, forgetfulness, images, and memories about memories, Augustine reads almost like Wittgenstein or Douglas Hofstadter. Book X is where this line is clearest. He discusses forecasting, and questions whether one can even be certain that that world we seem to be in exists and, if so, whether it has some discernible pattern we can \"learn\" through our own existence. It made me wonder, what does Augustine make of imagination and creativity? How does he think about human capacity to extrapolate to what might happen, our actions and their consequences?

There's another angle to the objectivity problem around what's real versus what's imagined that our group discussed. What would Augustine think about our Catherine Project classes over Zoom, and our virtual worlds, and the very rich life that XXI humans can build online? Are those better than the physical? Can you sin in the metaverse? Naturally, we landed in questions of free will.

While the book has many emotional scenes of grief and family drama, the most striking to me was Augustine recounting his time in Milan, and his encounter with a drunken beggar while on his way to deliver a speech. Augustine contrasts his position with the drunkard's, prompting him to question his ambitions and the nature of true happiness.

&gt; For what he had gained with a few coins, obtained by begging, that is the cheerfulness of temporal felicity, I was going about to reach by painfully twisted and roundabout ways. True joy he had not. But my quest to fulfill my ambitions was much falser. There was no question that he was happy and I racked with anxiety. He had no worries; I was frenetic, and if anyone had asked me if I would prefer to be merry or to be racked with fear, I would have answered \"to be merry\". Yet if he asked whether I would prefer to be a beggar like that man or the kind of person I then was, I would have chosen to be myself, a bundle of anxieties and fears. What an absurd choice! Surely it could not be the right one.

Much like the discussion on language I mentioned earlier, this passage could easily be a thought that someone like me could have walking the streets of San Francisco in the 2020s. The scene highlights the contrast between temporal pleasures and eternal joy, a theme that runs throughout the Confessions. The beggar's joy is fleeting and superficial, while Augustine searches for "true" joy and glory, which he believes can only be found in God. This realization leads him to renounce his worldly ambitions and embrace a life of faith and devotion. If only things were that easy.

The book is deeply introspective, but only offers incomplete answers. I am glad I got to read it with a group, as I'm not sure I would have gotten as much out of it on my own.

&lt;hr /&gt;

&lt;small&gt;
&lt;em&gt;Photo: Sagrada Familia, by me. Previously posted on &lt;a href="/photos/2024/07/03/barcelona/"&gt;Barcelona, 2024&lt;/a&gt;.
&lt;/em&gt;&lt;/small&gt;</description><author>Avy Faingezicht</author><pubDate>Mon, 30 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://faingezicht.com/articles/2024/12/30/confessions/</guid></item><item><title>2024 in review</title><link>https://codakuma.com/2024-in-review/</link><description>How 2024 went for me and my apps</description><author>Codakuma</author><pubDate>Mon, 30 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://codakuma.com/2024-in-review/</guid></item><item><title>Reflecting on 2024, preparing for 2025</title><link>https://ntietz.com/blog/reflecting-on-2024-preparing-for-2025/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;If you do things a few times, they're a tradition.
This is the third time I'm writing one of these, so I guess it's an annual tradition now!
This is where I reflect on the year that's been, and talk some about my hopes and goals for the next year.&lt;/p&gt;
&lt;h1 id="reflecting-on-2024"&gt;Reflecting on 2024&lt;/h1&gt;
&lt;p&gt;This year has been a lot, and there are a few months of it that just feel like a black hole to me.
That's because I got sick in the middle of it.
I'm really proud of how much I got done in spite of being the sickest I've ever been.
And I'm excited to see what I can do next year, now that I'm nearly fully recovered.&lt;/p&gt;
&lt;h2 id="professional"&gt;Professional&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I spoke at a conference!&lt;/strong&gt;
This year marked my first ever conference talk.
Technically, my first one was at &lt;a href="https://sigbovik.org/2024/"&gt;SIGBOVIK 2024&lt;/a&gt;, but I'm really talking about !!con.
I've submitted talks to conferences before and this is the first one I've ever had accepted.
You can watch &lt;a href="https://www.youtube.com/watch?v=RQnL-McvL1o&amp;amp;list=PLofFli6PGTsDQQnaScOAXy3Dg1BKguKHP&amp;amp;index=2"&gt;the recording&lt;/a&gt;.
That link takes you to the playlist of all !!con talks from this (final) year, so please enjoy them all!&lt;/p&gt;
&lt;p&gt;It was an incredible experience.
The whole conference felt like I was with old friends who I just hadn't met yet.
It made me remember the power of connecting with other nerds in physical space.
And it reminded me of the joy of being on a stage.
More on that in the personal section.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I wrote even more than last year.&lt;/strong&gt;
My goal for this year was to continue my status quo: publish at least one blog post each week.
I overshot this again, with 60 blog posts over 90,000 words.
The most important thing for me has been consistency.
By writing every week, I've been able to continue to use this momentum to stretch my creative practice.
This even held during my illness this summer, and it was something for me to hold on to when I could do little else.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I got paid for my writing.&lt;/strong&gt;
This is the first time I have been paid explicitly &lt;em&gt;to write&lt;/em&gt;.
I was sponsored to write a post about an open-source product (and the contract even requires that I misuse it, on purpose, since that's what I pitched).
The overall experience was pretty good, and I'm also not sure I would do it again in the near future: while I'm working a day job, I don't want to spend my limited writing time on things I'm not already self-motivated to publish.
But—I dearly want to find a way to get paid for my writing which &lt;em&gt;isn't&lt;/em&gt; sponsored posts.
This might look like a Patreon or similar, so let me know if you're interested, and you might be the nudge to get me over that finish line.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I started coaching people.&lt;/strong&gt;
This year saw me take on my first three coaching clients.
These were all pro bono, friends of mine who needed some help with career questions and technical leadership development.
It's been an incredible experience, getting to directly help people grow and overcome their challenges.
(If you're interested in being coached by me on technical leadership, &lt;a href="mailto:me@ntietz.com"&gt;reach out to me&lt;/a&gt;! You're a particularly good fit if you're a senior or staff software engineer aiming to level up, and members of marginalized groups are who I'm most hoping to help.)&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I grew as a leader.&lt;/strong&gt;
Most of my reflection here is private because it's so intertwined with specific leadership challenges at my day job, but it's been a really helpful year for me in my leadership development.
I've learned a lot, and I've seen a lot of old decisions come around to their conclusion to complete my learning arc.&lt;/p&gt;
&lt;h2 id="personal"&gt;Personal&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;I got sick.&lt;/strong&gt;
It's hard to say when exactly I got sick, but my symptoms got to where I had to go to my doctor at the start of May, and my full recovery started in November.
I am pretty sure I was sick before that, since I had started needing more and more sleep throughout the spring, but it's impossible to say at this point.&lt;/p&gt;
&lt;p&gt;At the peak of my illness, I was in near constant pain (about a 6 on the pain scale) and could not stand up for 5 minutes without having tachycardia.
If that happened, I had to lie on my back until my heart rate came back down.
Walking around the block was an impossibility, when months prior I was running 25 miles a week and hauling the kids around on my bicycle.&lt;/p&gt;
&lt;p&gt;I bounced through multiple doctors.
We hit the end of my general practitioner's expertise, so she referred me to a GI practice since our core symptom was related to my liver and abdominal pain.
(The liver was ultimately a red herring: what looked unusual on my ultrasound was, with further testing, not concerning.)
The GI practice did a lot of tests, and I had a lot of waiting (GI docs are in such high demand, you can't see them), but ultimately... they also found nothing.
Meanwhile I was still in pain, and could do very little.&lt;/p&gt;
&lt;p&gt;Around this point, I went on medical leave with my employer.
Before that I had been working as much as I was able, and contributing something of value, but it had become clear that I was going to need to focus on my recovery if I wanted to actually get to the bottom of this.
Going on medical leave was &lt;em&gt;terrifying&lt;/em&gt;, because it meant I would be without income for months.
But it was ultimately the right decision.&lt;/p&gt;
&lt;p&gt;Around this same time, I went to my third doctor.
She got me a diagnosis.
A friend sent me to her, since she's a specialist in conditions that present with ambiguous symptoms and chronic fatigue.
This doctor ran a lot of tests—expensive tests, which insurance doesn't cover—and we ultimately got me a firm diagnosis six months into the whole ordeal.
After starting treatment, it's been a very fast recovery: almost two months in, and I'm over 90% normal.
How our definitions of "fast" shift.
I may have relapses in the future, there's no way to know, but I'm relieved to know what is going on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I stopped running.&lt;/strong&gt;
The pain started in May when I was running, so I had to abruptly go from running 25 miles a week to not at all.
Eventually it moved from pain while running to constant pain.
And then eventually... it faded away entirely, as long as I keep up on one of my medications.
Now I can run if I want to—but I've decided not to.&lt;/p&gt;
&lt;p&gt;For the last decade, I've identified as a runner.
I did a few half marathons with reasonable times, and I did a marathon in 4:07, finishing in 75 F heat.
For much of the decade before that, I identified as a cyclist.
I was into cycling from the moment I could get on a bike, always wanting to go further and faster.
In high school, I did a 100 mile bike ride.&lt;/p&gt;
&lt;p&gt;Now that I've been forced to take a break from it, I've realized I'm pretty content with not putting in the grueling schedule needed to get back to the high level of performance I thought I wanted to target next year.
I might get back into this sooner or later, but right now, I'm working on functional strength and being healthy and having more balance in my life.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I got back into music!&lt;/strong&gt;
When I was sick, I bought a wind synth and started playing music again.
Then I started taking lessons with an incredible teacher who's an accomplished musician in his own right.
And then I got a drum pad, another wind synth, a hand-me-down keyboard, and got my clarinet back out.
I've fallen deep into this and I'm loving every single minute of it, frustration and all.&lt;/p&gt;
&lt;p&gt;This is replacing a lot of the dedication and discipline I used to get from endurance exercise.
It has the added benefit of creating art in the process, which heals my soul.
The deep breathing involved in playing wind instruments certainly helps me as well.&lt;/p&gt;
&lt;p&gt;I'm learning some music theory, and it's hard!
I want to learn how to write songs and compose music, and I'm going to get there.
If you have any favorite resources for this, please &lt;a href="mailto:me@ntietz.com"&gt;send them to me&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;The upshot of getting back into music is that it will, hopefully, give me a way to perform again.
I've been a performer in some aspect for a lot of my life.
In school, I was in our concert bands and in small wind ensembles.
I was on the debate team, a shock to people who knew me as the shy kid who shook when forced to speak in front of the class.
Since school, though, I've lost this opportunity.
I got a taste of performance again with my conference talk, and I think music will be my route to performing regularly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I'm starting to use my voice.&lt;/strong&gt;
Advocacy and activism were always things I looked up to but didn't feel like I was able to do.
But then I found I have a voice, and I realized I need to use it.
I shared a few posts this year on things that are important and required me to speak up, like trans rights and the crisis that was happening in Asheville.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Organized two rated chess tournaments.&lt;/strong&gt;
This year I organized two tournaments for our local chess club!
They had about 12 players each, and went off smoothly.
It was a good experience all around.
I'm not sure I'll have the opportunity (or desire) to do this again this year.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Kept my head up.&lt;/strong&gt;
Current events have been... a lot... and I've managed to keep my head up for most of it.
I'm going to keep going, and keep trying.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Upgraded the workshop for all-year use.&lt;/strong&gt;
Historically I've only been able to use my workshop for a few months of the year when the weather is right.
This year, we got it insulated, replaced the windows, and added a heat pump, and all those combine to mean I can keep it temperate all year so I can go out there whenever I want.
(Some wood finishes are inadvisable in cold or hot weather since they need good ventilation, but otherwise any time for anything.)&lt;/p&gt;
&lt;p&gt;It's changed my relationship with woodworking, since now I can pop out there and make something &lt;em&gt;whenever I want&lt;/em&gt;.
When I was rearranging my desk yesterday, I realized I really needed a headphone stand.
And so I popped out to the workshop and put in a total of two hours of work (split across a few sessions for glue and finish to dry), and now I have one!
Something similar happened when I needed an adapter to mount my drum pad: I just &lt;em&gt;made&lt;/em&gt; it.&lt;/p&gt;
&lt;h1 id="last-year-s-goals"&gt;Last year's goals&lt;/h1&gt;
&lt;p&gt;This year was a lot!
How does it stack up against what I &lt;a href="https://ntietz.com/blog/reflecting-on-2023-preparing-for-2024/"&gt;wanted to do last year&lt;/a&gt;?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;❓ I wanted to keep my rights, and I did—for now, in my own state. But it's really tenuous, and there are many states where I'd be punished for using a bathroom. The state I was born in, Ohio, has banned trans folks from using the bathrooms consistent with their gender, and the incoming administration is a dark cloud. I won't call this a miss yet, but I can't call it a win.&lt;/li&gt;
&lt;li&gt;✅ No personal side projects went into production! I once again toyed with the idea and once again talked myself out of it. Good job, me!&lt;/li&gt;
&lt;li&gt;✅ I am not sure I struck a better balance with calls and making, but I embraced that I love talking to my friends and just continued to make time for them. This is going to be even more important next year.&lt;/li&gt;
&lt;li&gt;✅ I kept writing on the same schedule, and I did expand it! I did a creative writing class, and even wrote a poem as well.&lt;/li&gt;
&lt;li&gt;❌ I did &lt;em&gt;not&lt;/em&gt; do any comedy this year, so it's a miss. But it's a happy miss, because I found other things that I was drawn to.&lt;/li&gt;
&lt;li&gt;✅ I stayed pretty active in my communities, given my health.&lt;/li&gt;
&lt;li&gt;✅ I was a good parent and partner, given my health.&lt;/li&gt;
&lt;li&gt;✅ I finished voice training! This was almost a gimme, since I was done in January this year.&lt;/li&gt;
&lt;li&gt;✅ My ergonomic setup was &lt;a href="https://ntietz.com/blog/evolving-ergo-setup/"&gt;definitely improved&lt;/a&gt;. I still want to work on using Talon more, but I have made improvements there as well.&lt;/li&gt;
&lt;li&gt;❌ I did &lt;em&gt;not&lt;/em&gt; do more technical projects this year. I've started a few, and I'm going along in the background, but health got in the way.&lt;/li&gt;
&lt;li&gt;❌ I did &lt;em&gt;not&lt;/em&gt; get back into competitive chess, and I probably won't. It simply doesn't feel as important right now. Music is filling the role that it filled in my life.&lt;/li&gt;
&lt;li&gt;✅ I kept my mental health strong!&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I think I did really well on these goals, even if it were a normal year and not a year where I spent 6 months sick to various degrees and where I started to feel the crushing weight of our politics and its threat on my rights and my life.
This year I did learn a bit about what is important to me, and where I want to spend my time.
That's reflect in my hopes and goals for next year.&lt;/p&gt;
&lt;h1 id="hopes-and-goals-for-2025"&gt;Hopes and goals for 2025&lt;/h1&gt;
&lt;p&gt;These aren't predictions or concrete goals, but a reflection on what I'd &lt;em&gt;like&lt;/em&gt; the next year to be.
This is what I hope 2025 looks like for me.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep my rights.&lt;/strong&gt;
A perennial goal at this point, it's the headliner since trans rights are near the top of the Republican's agenda for this new administration.
I'm wary, and I'm going to do what I need to do to keep myself and my family safe.
I think that can be done from where I am in a safe, supportive community, but we will keep ourselves safe while continuing to advocate for &lt;em&gt;all&lt;/em&gt; those who need protection.
In particular, I'm going to keep living my best life and being positive representation for other trans folks who are similarly under attack.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No personal-time side projects into production.&lt;/strong&gt;
This one will probably be a forever anti-goal for me.
I don't want to do ops-y things in my free time (despite feeling like shaving that yak occasionally), and I don't want to support a product in my free time.
My free time is more about playful exploration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Maintain relationships with friends and family.&lt;/strong&gt;
This is the 2025 version of 2024's goal of "strike a better balance with calls and making."
I'm positive I'll have time set aside for making things and for playing music.
But this is going to be a challenging year, so my loved ones (given/chosen family and dear friends) will need my support and I will need theirs.
So I'm going to put those relationships first and foremost.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Explore ways to make this my living.&lt;/strong&gt;
I want to do more playful exploration, the kinds of things I do on my blog, and make that my living eventually!
2025 isn't when I'll get there, but I want to try out one or two things (like a Patreon? fund myself via consulting and coaching?) to start understanding what might work for both me and my readers.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep my mental health strong.&lt;/strong&gt;
This is going to be a challenge, and I am in a good spot for it.
I'll need to dedicate effort to it, though, what with the upcoming onslaught.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Release some recorded music.&lt;/strong&gt;
I'm working on a lot of aspects of my music.
Eventually, I want to write something and release it.
This year might be releasing a recording of a cover, but it might also be an original piece.
I'm not sure!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write some original music.&lt;/strong&gt;
I don't know if I'll release something of my own, but I know I need to work on it.
We'll see how this goes!
It's scary to me, and it's also something I'm confident I can do if I put in the effort to learn how.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do some ridiculous fun projects with code.&lt;/strong&gt;
There are a few things I &lt;em&gt;really&lt;/em&gt; wanted to work on in 2024 that are just playful, fun, ridiculous things.
I didn't get to do them since I was, uh, kinda sick (not sure if I mentioned that yet).
So they're burning to get out of me this year.
I want at least &lt;em&gt;one&lt;/em&gt; to get out of me this year.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;That's it!
I've poured a lot of myself into this post.
If you've made it this far: thank you, so much, for reading.&lt;/p&gt;
&lt;p&gt;2024 had a lot in it, good and bad.
I'm trying to hold both at the same time, to remember the good and remember the bad, as they are both important aspects of the year for different reasons.&lt;/p&gt;
&lt;p&gt;I hope that 2025 keeps much of the good, and that we can minimize the bad.
I'm going to do everything I can to hold joy in this world.
Please join me in that, and let's fill 2025 with joy, even in the face of all that's being thrown at us.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 30 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/reflecting-on-2024-preparing-for-2025/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Goals for 2025</title><link>https://chrisfrew.in/blog/goals-for-2025/</link><description>&lt;div style="width: 100%; margin: 0 auto; padding: 40px 40px;"&gt;
                    &lt;p&gt;
                      There's a new blog post &lt;em&gt;"Goals for 2025"&lt;/em&gt; and you can &lt;a href="https://chrisfrew.in/blog/goals-for-2025/"&gt;read it online&lt;/a&gt;.
                      &lt;br /&gt;
                      undefined
                      &lt;br /&gt;
                      You can also &lt;a href="https://chrisfrew.us19.list-manage.com/subscribe/post?u=5f7289fbe97df30f673068826&amp;amp;id=b1729bbdce"&gt;subscribe&lt;/a&gt; for weekly emails on my latest posts - if I publish anything that week!
                    &lt;/p&gt;
                  &lt;/div&gt;</description><author>Chris' Full Stack Blog RSS Feed</author><pubDate>Mon, 30 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://chrisfrew.in/blog/goals-for-2025/</guid></item><item><title>Prefer Numbered Lists to Bullets</title><link>https://www.dannyguo.com/blog/prefer-numbered-lists-to-bullets</link><author>Danny Guo</author><pubDate>Mon, 30 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.dannyguo.com/blog/prefer-numbered-lists-to-bullets</guid></item><item><title>Reduce JavaScript Bundle Sizes in SvelteKit by using server load functions</title><link>https://khromov.se/optimize-javascript-bundle-sizes-in-sveltekit-by-using-server-load-functions/</link><description>&lt;p&gt;The amount of JavaScript you ship to your users directly impacts your site&amp;#8217;s performance. A smaller bundle size means faster initial loading times and an overall snappier experience for end users. In this post, I&amp;#8217;ll show you how to analyze your SvelteKit bundle size and optimize it by moving heavy dependencies to the server. Analyzing Bundle Sizes Before we start, let&amp;#8217;s set up a tool to visualize our bundle sizes. We&amp;#8217;ll use rollup-plugin-visualizer, which works great with SvelteKit. Install the package into your existing SvelteKit project: Configure it in vite.config.ts: When you build your project with npm run build, you&amp;#8217;ll [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://khromov.se/optimize-javascript-bundle-sizes-in-sveltekit-by-using-server-load-functions/"&gt;Reduce JavaScript Bundle Sizes in SvelteKit by using server load functions&lt;/a&gt; appeared first on &lt;a href="https://khromov.se"&gt;Stanislav Khromov&lt;/a&gt;.&lt;/p&gt;</description><author>Stanislav Khromov</author><pubDate>Mon, 30 Dec 2024 01:06:04 GMT</pubDate><guid isPermaLink="true">https://khromov.se/optimize-javascript-bundle-sizes-in-sveltekit-by-using-server-load-functions/</guid></item><item><title>Modern Portfolio Theory III: The Efficient Frontier in log volatility—return space</title><link>https://bytepawn.com/modern-portfolio-theory-efficient-frontier.html</link><description>&lt;p&gt;In the previous two articles, we explored the coverage of random portfolios in log volatility—return space, both with and without a risk-free asset. We now take the next step, and calculate the Efficient Frontier and Capital Market Line of Markowitz’s theory.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/efficient-frontier-2.png" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 30 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/modern-portfolio-theory-efficient-frontier.html</guid></item><item><title>AI 产品的破局之道：以人为本</title><link>https://www.4async.com/2024/12/human-centered-product-design-with-human-in-the-loop/</link><description>&lt;img alt="Featured image of post AI 产品的破局之道：以人为本" src="https://www.4async.com/2024/12/human-centered-product-design-with-human-in-the-loop/cover.webp" /&gt;&lt;p&gt;伴随着 OpenAI、Anthropic 等等公司的努力，大型语言模型（LLM）的性能不断提升，AI 产品的应用场景也越来越广泛。从口袋里的智能助手到驰骋未来的自动驾驶，从辅助医疗诊断的慧眼到洞察市场风云的智脑，AI 正以前所未有的速度改变着世界。但是在这个过程中，我们会注意到一些问题，我们会陷入“技术至上”的误区，过分强调技术的先进性，而忽略了用户的需求和体验。这就导致了很多 AI 产品虽然技术先进，但却无法真正满足用户的需求，最终无法实现商业价值。究其原因，许多 AI 系统在与人类交互时，常常陷入**“鸡同鸭讲”的窘境**——对上下文的理解捉襟见肘，难以领会人类语言中的微妙含义；更令人担忧的是“幻觉”问题，AI 煞有介事地编造着“一本正经的胡说八道”，让用户无所适从；而在用户体验方面，许多 AI 产品更是差强人意，机械式的交互和冰冷的界面让人难以产生亲近感。有些时候我们有些失望：&lt;a class="link" href="https://zh.wikipedia.org/zh-hans/%E5%B8%95%E7%B4%AF%E6%89%98%E6%B3%95%E5%88%99" rel="noopener" target="_blank"&gt;帕累托法则&lt;/a&gt;（或者叫做 28 原则），一直在发挥着神秘的力量。&lt;/p&gt;
&lt;p&gt;抛开模型开发公司本身而言，面向客户的 AI 产品的设计和开发是一个复杂的过程，需要综合考虑技术、商业和用户体验等多个方面。在这个过程中，如何保证产品的质量和用户体验成为了关键问题。传统的产品设计方法往往是由设计师和工程师等专业人员完成，而用户往往只是产品的使用者。然而，随着 LLM 的发展，新的交互形式不停的出现，比如之前的聊天产品和语音助手交互产品普遍反馈交互感太差并不实用。新的实时多模态模型的出现，让这些可以更好的理解用户的需求，更好的满足用户的需求。&lt;/p&gt;
&lt;p&gt;但，无论如何，要充分释放 AI 的潜力，必须将用户需求和体验置于核心地位。这不仅仅是一句口号，更是一种深刻的变革，要求我们将用户的需求、期望和体验置于 AI 产品设计的最核心。这意味着，我们需要像人类学家一样去观察和理解用户的真实诉求，像心理学家一样去揣摩用户的情感和认知模式，并将这些洞察深深地烙印在产品的基因之中。只有这样，才能打造出真正被用户拥抱、信任并从中受益的 AI 产品。&lt;/p&gt;
&lt;p&gt;本文就是我在最近两年开发 AI 产品的一些体悟，希望能够给 AI 产品的设计和开发带来一些启发。虽然我是一个工程师视角，但是斗胆今天就来聊一聊 AI 产品的设计和体验问题。&lt;/p&gt;
&lt;h2 id="理解当前-ai-的阶段能力与局限"&gt;理解当前 AI 的阶段：能力与局限
&lt;/h2&gt;&lt;p&gt;毋庸置疑，现在 AI 在“力大砖飞”的背景之下，已经在许多领域展现出了强大的能力。从自然语言处理到计算机视觉，从智能推荐到智能对话，AI 的应用场景越来越广泛，技术也越来越成熟。在过去的两年中，我们见证了每隔几个月到半年，LLM 就进行了一种大范围的能力提升变革。然而，AI 的能力和局限性也给产品设计带来了许多挑战。这些局限性并非是无法克服的，有些也仅仅局限于当前的技术水平，但是我们必须清楚地认识到这些局限性，才能更好地设计和开发 AI 产品。&lt;/p&gt;
&lt;p&gt;这些问题你可能在很多地方看到过，让我再赘述一遍：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;上下文问题&lt;/strong&gt;：大模型在处理长文本或复杂对话时，受限于上下文“记忆”能力，可能导致理解偏差和信息遗漏。例如，在长篇文档总结或多轮对话中难以保持信息连贯。扩展上下文窗口当然是一种解决方案，但即便是大上下文窗口的 Gemini，也会出现注意力不集中的问题，最终影响到模型的性能和结果。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;幻觉问题&lt;/strong&gt;：AI 可能在缺乏事实依据的情况下生成看似合理但虚假的信息，严重影响用户信任，尤其在需要高准确性的场景中。在绝大多数的 B 端场景和部分 C 端场景中，我们都需要确保回答的准确性。之前也有&lt;a class="link" href="https://news.qq.com/rain/a/20240223A012Z100" rel="noopener" target="_blank"&gt;加拿大航空的案例&lt;/a&gt;，导致公司的信誉受损。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;缺乏常识与情感理解&lt;/strong&gt;： AI 在处理需要常识判断和情感理解的场景中表现不足，导致交互生硬，甚至产生误解。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这些问题并非无法解决，但是我们必须清楚地认识到这些问题，才能更好地设计和开发 AI 产品。在 AI 产品设计中，我们需要充分考虑这些问题，从而更好地满足用户的需求和体验。而在这个过程中，我认为针对人机回环(Human in the loop, HITL)的设计思想是非常重要的。通过完善这个设计思想，我们可以更好地解决上述问题，提高 AI 产品的质量和用户体验。&lt;/p&gt;
&lt;h2 id="以人为本产品的设计灵魂"&gt;以人为本：产品的设计灵魂
&lt;/h2&gt;&lt;p&gt;以人为本，是产品设计的灵魂所在，对于 AI 产品而言更是如此。对这种方式而言，一般会以下面的方式为循环来进行：&lt;/p&gt;</description><author>ipfans's Blog</author><pubDate>Mon, 30 Dec 2024 00:30:00 GMT</pubDate><guid isPermaLink="true">https://www.4async.com/2024/12/human-centered-product-design-with-human-in-the-loop/</guid></item><item><title>Short story fika: Duty</title><link>https://liza.io/short-story-fika-duty/</link><description>&lt;p&gt;We had our fourth short story fika yesterday. This time, the theme was &amp;lsquo;duty&amp;rsquo; and the stories featured were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The Sniper by Liam O&amp;rsquo;Flaherty&lt;/li&gt;
&lt;li&gt;The Lottery by Shirley Jackson&lt;/li&gt;
&lt;li&gt;The Veldt by Ray Bradbury&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It was just three of us this time, and we had a great chat about the duties and sacrifices of war, the questioning (or lack thereof) of outdated traditions, and the costs of our increasing reliance on automation in daily life.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Sun, 29 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/short-story-fika-duty/</guid></item><item><title>Reading Highlights 2024</title><link>https://rohitjha.com/blog/reading-highlights-2024/</link><description>&lt;p&gt;2024 was  challenging and rewarding in equal measure when it came to me being able to set time aside for reading. Lots of travel across multiple oceans and much time spent in hotels, Airbnbs and flights. So I switched to a more multi-device, multi-platform approach. My main platform for reading&lt;/p&gt;</description><author>THINK@RJ</author><pubDate>Sun, 29 Dec 2024 20:43:00 GMT</pubDate><guid isPermaLink="true">https://rohitjha.com/blog/reading-highlights-2024/</guid></item><item><title>Roast #43: 2024 Year-end wrap up</title><link>https://thoughtfulcoffeenyc.substack.com/p/roast-43-2024-year-end-wrap-up</link><description>stumbling, but finishing.</description><author>thoughtfulcoffee</author><pubDate>Sun, 29 Dec 2024 20:17:58 GMT</pubDate><guid isPermaLink="true">https://thoughtfulcoffeenyc.substack.com/p/roast-43-2024-year-end-wrap-up</guid></item><item><title>What I read in 2024</title><link>https://ernest.oppet.it/2024/12/29/what-i-read-in-2024/</link><description>Here is what I read in 2024. Did much more sampling and dropping books halfway (I still love you, Moby Dick) so it’s a shorter list than last year. Hope you find a gem or two in there for your reading lists. Happy new year! 🤓 Non-Fiction (English) Fiction (French) Non-Fiction (French)</description><author>Ernest Oppetit</author><pubDate>Sun, 29 Dec 2024 14:40:26 GMT</pubDate><guid isPermaLink="true">https://ernest.oppet.it/2024/12/29/what-i-read-in-2024/</guid></item><item><title>Booksbooksbooks 12/24</title><link>https://goodinternet.substack.com/p/booksbooksbooks-1224</link><description>Tiny reviews for books from Shirley Jackson, Martha Wells, Alexandre Dumas, Stanislaw Lem, Stephen Baxter, Stephen King, Adrian Tchaikovsky, Dan Jones, Carlo Rovelli and more.</description><author>GOOD INTERNET</author><pubDate>Sun, 29 Dec 2024 13:45:52 GMT</pubDate><guid isPermaLink="true">https://goodinternet.substack.com/p/booksbooksbooks-1224</guid></item><item><title>Intro to Astro</title><link>https://www.danielcorin.com/til/astro/intro/</link><description>Intro to Astro</description><author>Thought Eddies</author><pubDate>Sun, 29 Dec 2024 13:24:49 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/astro/intro/</guid></item><item><title>I Am Fine</title><link>https://thedarkside.frantzmiccoli.com/experimentations/2024/12/29/i-am-fine.html</link><description>&lt;p&gt;Caveat: I do not have any qualification in medicine or biology.&lt;/p&gt;

&lt;p&gt;Hyperuricemia is a condition where acid uric in your blood is above a certain 
concentration, this condition, if lasting for long enough, can cause 
&lt;a href="https://en.wikipedia.org/wiki/Gout"&gt;gout&lt;/a&gt;, which
is something that you certainly do not want.&lt;/p&gt;

&lt;h2 id="i-have-hyperuricemia"&gt;I have hyperuricemia&lt;/h2&gt;

&lt;p&gt;As part of my routines checks and for some travel preparations I visited my GP,
among the routines check was a blood check that I did on the spot.&lt;/p&gt;

&lt;p&gt;The result came indicating a level of 8.7 mg/L of uric acid which is above 
the upper bracket of the normal range indicated on the document at 7.7 mg/L 
(which I think is 0.7 mg/L above of the recommended maximal value in the US 
and 0.5 mg/L above what it was a few years ago here).&lt;/p&gt;

&lt;h2 id="really"&gt;Really?&lt;/h2&gt;

&lt;p&gt;My previous control, two months before, was at this upper bracket. As I like to
think I have control on my life, I tend to overlook the non-actionable 
elements that could explain this situation.
However, when reading online 
&lt;a href="https://my.clevelandclinic.org/health/diseases/17808-hyperuricemia-high-uric-acid-level"&gt;the main lifestyle factor for acid uric seems to be purine rich food&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Red meat&lt;/li&gt;
  &lt;li&gt;Seafood&lt;/li&gt;
  &lt;li&gt;Food and drinks with high fructose&lt;/li&gt;
  &lt;li&gt;Alcohol (including alcohol-free beer)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Food accounts for only 12% of the problem but that’s still where I think I have 
most leverage.&lt;/p&gt;

&lt;p&gt;And I am pretty much a monk when I read that list. Not a perfect monk, but 
a monk-leaning person. I consume a bit of seafood but rarely, 
I drank little alcohol in the last month (probably less than 7 units a week), 
it is true that I drank a bit of alcohol-free beer but that too was a couple of 
occurrences a week. Red meat, especially last month was a rare thing.&lt;/p&gt;

&lt;p&gt;My protein sources are mostly chicken, tofu and whey isolate protein powders.&lt;/p&gt;

&lt;p&gt;I ended-up looking up if those last ones (protein powders) had an impact as 
I indeed was consuming more of
them over the last two months. I did not find a reliable source, the messages 
were conflicting.&lt;/p&gt;

&lt;p&gt;Then I remembered a remark from my GP: “sport has an impact on uric acid”, and
searching around that idea was interesting. I found a paper from 1980, 
&lt;a href="https://pubmed.ncbi.nlm.nih.gov/7374440/"&gt;Purine Metabolism During Strenuous Muscular Exercise in Man (Sutton &amp;amp; al., 1980)&lt;/a&gt;,
and this was getting interesting:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;After a 5 km race, one group saw its mean uric acid increase from 6.9 to 8.5 
mg/dL&lt;/li&gt;
  &lt;li&gt;After a marathon, another group went from 6.2 to 7.9 mg/dL.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Suddenly, I thought about my agenda :&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Day before the blood sample at 20h to 21h: confined scuba diving training.&lt;/li&gt;
  &lt;li&gt;Day of the blood sample 5h30 to 7h: leg workout at the gym.&lt;/li&gt;
  &lt;li&gt;Blood sample at 8h45.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Since I am tracking all my food and water intake. I know my exact diner from the 
night before.&lt;/p&gt;

&lt;p&gt;I was suddenly thinking that maybe I created an intense bias in the 
measurement by working out.&lt;/p&gt;

&lt;h2 id="getting-more-data-points"&gt;Getting more data points&lt;/h2&gt;

&lt;p&gt;I decided to reproduce the same situation and gather more data points
to see if there is indeed an increase following physical activity. Properly
quantifying it will not be possible on a single episode, but that could give me
an idea.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Day -2: rest &amp;amp; 2 350 mL water intake&lt;/li&gt;
  &lt;li&gt;Day -1, rest &amp;amp; 1 830 mL water intake&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Day -1, 7:37 before breakfast: 7.9 mg/dL&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Day -1, evening: confined scuba diving training (luckily I still had some in 
my agenda).&lt;/li&gt;
  &lt;li&gt;Day -1, evening: identical diner, including 660 mL alcohol-free beer 
(not accounted in water intake).&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Day 0, 6:35: 7.8 mg/dL&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Day 0, 6:45 - 8:15: leg training, 600ml water intake&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Day 0, 10:05 before breakfast: 7.8 mg/dL&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Day 0, 15:00: 7.6 mg/dL&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Day +1, 6:44 before breakfast: 6.7 mg/dL&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="conclusion-nothing"&gt;Conclusion: nothing&lt;/h2&gt;

&lt;p&gt;At this point, I felt kind of frustrated: because the new data was not showing
much. The good news is that it seems that the sample that triggered all this 
was probably not characterizing my general state. The other interesting piece
of information is that I did not reproduce the results of the paper mentioned 
above, either:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The uric acid spike is very limited in time (they measured uric acid right 
after exercise, I waited for two hours).&lt;/li&gt;
  &lt;li&gt;The nature of the work-out was too different to trigger similar results.&lt;/li&gt;
  &lt;li&gt;I was an outlier that week.&lt;/li&gt;
  &lt;li&gt;Something wrong with the study.&lt;/li&gt;
&lt;/ul&gt;</description><author>The Dark Side</author><pubDate>Sun, 29 Dec 2024 12:08:36 GMT</pubDate><guid isPermaLink="true">https://thedarkside.frantzmiccoli.com/experimentations/2024/12/29/i-am-fine.html</guid></item><item><title>My first Rust crate</title><link>https://www.mootoday.com/blog/my-first-rust-crate</link><description>I released `clap-nested-commands` to reduce boilerplate code for Rust CLIs built with `clap`.</description><author>www.mootoday.com - RSS Feed</author><pubDate>Sun, 29 Dec 2024 05:13:05 GMT</pubDate><guid isPermaLink="true">https://www.mootoday.com/blog/my-first-rust-crate</guid></item><item><title>Sunset Orange</title><link>https://www.unsungnovelty.org/gallery/sunset-orange/</link><description/><author>unsungNovelty</author><pubDate>Sun, 29 Dec 2024 02:42:40 GMT</pubDate><guid isPermaLink="true">https://www.unsungnovelty.org/gallery/sunset-orange/</guid></item><item><title>An Utterly Incomplete Look at Research from 1824</title><link>http://bcmullins.github.io/research-from-1824/</link><description>The French Revolution cast a long shadow over early nineteenth century thought. Many of the selections below are concerned with the aftermath of the Revolution and the Napoleonic Wars both economically and intellectually. The pseudonymous Piercy Ravenstone critiques England’s system of public finance and its handling of the massive debt resulting from the wars. John Stuart Mill reviews a debate about the effects of war-time spending on prices. His father, James Mill, offers analyses of government, jurisprudence, freedom of the press, and international law along utilitarian lines for the Encyclopedia Britannica. William Stevenson argues that the moral convulsion among the people caused by the French Revolution opened the door for intellectual progress.</description><author>Brett Mullins</author><pubDate>Sun, 29 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://bcmullins.github.io/research-from-1824/</guid></item><item><title>Optimising my Rust solutions for Advent of Code</title><link>https://nindalf.com/posts/optimising-rust/</link><description>I completed Advent of Code 2024 in Rust. Here are all the tricks I used to optimise my code.</description><author>Krishna's blog</author><pubDate>Sun, 29 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nindalf.com/posts/optimising-rust/</guid></item><item><title>O-1s, H-1Bs, and other ways US Immigration is Broken</title><link>https://caseysoftware.com/blog/o-1s-h-1bs-and-other-ways-us-immigration-is-broken?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=o-1s-h-1bs-and-other-ways-us-immigration-is-broken</link><description>&lt;p&gt;With the big ongoing debate on immigration, it&amp;#8217;s been amazing/horrifying watching people muddle everyone together into one bucket and then&amp;#8230;&lt;/p&gt;
The post &lt;a href="https://caseysoftware.com/blog/o-1s-h-1bs-and-other-ways-us-immigration-is-broken"&gt;O-1s, H-1Bs, and other ways US Immigration is Broken&lt;/a&gt; appeared first on &lt;a href="https://caseysoftware.com"&gt;Caseysoftware&lt;/a&gt;.</description><author>Caseysoftware</author><pubDate>Sat, 28 Dec 2024 18:37:27 GMT</pubDate><guid isPermaLink="true">https://caseysoftware.com/blog/o-1s-h-1bs-and-other-ways-us-immigration-is-broken?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=o-1s-h-1bs-and-other-ways-us-immigration-is-broken</guid></item><item><title>Favourite music</title><link>https://qubyte.codes/blog/favourite-music</link><description>&lt;p&gt;Here are a selection of albums which are all-time favourites of mine. They stand
up as a whole (not just as single tracks). I don’t claim to have good taste in
music, or that my taste is better than anyone else’s! With each album I’ve
written a little about times and places I associate with it. The albums are
presented in no particular order. I’ll garden this post over time and add new
material. I've linked to music services where I can find each album available,
but with minimal effort. There are other services, but they're either behind
paywalls or incomprehensible to me.&lt;/p&gt;
&lt;h2 id="devin-townsend--terria"&gt;Devin Townsend – Terria&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for Terria" class="u-photo" height="150" src="/images/fair-use/terria.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devintownsendofficial.bandcamp.com/album/terria"&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/6w2kw48F3xyHLgY4GDwFRV"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/terria/1045112739"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was the first music from Devin Townsend which I was exposed to. It was in
2002 I think, and I was listening more or less exclusively to bad metal at the
time. The weight of the sound of this album was what lured me in (particularly
Earth Day), but the progressive rock feeling to it led to a diversification of
my taste. This album evokes a feeling of vast landscapes and greenery. It, and
Ocean Machine remind me of my undergraduate years at Sussex University.&lt;/p&gt;
&lt;p&gt;To this day I’m a big fan of Townsend. No two offerings are the same, and some
of his stuff is really out there.&lt;/p&gt;
&lt;h2 id="strapping-young-lad--city"&gt;Strapping Young Lad – City&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for City" class="u-photo" height="149" src="/images/fair-use/city.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://centurymedia.bandcamp.com/album/city-remastered-demo-versions"&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/78Y2OaDAdvEqs3TRdCRdZc"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/city/1045623722"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I sneaked in another Devin Townsend album. Until about the mid 2000s Devin
Townsend also headed the metal band Strapping Young Lad (SYL). I’m less fond of
the output of SYL these days, but I still come back to City. It’s probably the
finest industrial metal album out there. This album also reminds me of my time
at Sussex University. That, and my questionable taste in clothing at the time
(very baggy, not a lot of colour, tassels).&lt;/p&gt;
&lt;h2 id="portishead--dummy"&gt;Portishead – Dummy&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for Dummy" class="u-photo" height="150" src="/images/fair-use/dummy.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/3539EbNgIdEDGBKkUf4wno"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/dummy/1440653096"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While at university I had a complicated relationship with a woman who introduced
me to Portishead. Things between us ended poorly, and this album reminds me of a
time when my head was in bad shape. I can listen to it now with more
objectivity, even if it makes me uncomfortable. The music should require no
introduction. The hauntingly beautiful vocals of Beth Gibbons and trip-hop
sound are timeless.&lt;/p&gt;
&lt;h2 id="isis-the-band--panopticon"&gt;ISIS (the band) – Panopticon&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for Panopticon" class="u-photo" height="150" src="/images/fair-use/panopticon.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://isistheband.bandcamp.com/album/panopticon-remastered"&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/4YVSY6TwnXWH7Jz4olWO1e"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/panopticon-remastered/1001664659"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While a postgrad at Leeds University my like of metal and progressive rock
mutated into a like of post-rock. ISIS (long before the terrorist group
sometimes affiliated with that initialism came to be) was a heavy post-rock
/ metal group which produced some extraordinarily heavy music. Panopticon is my
favourite of their albums, to the point that I named my first open source
offering after it. The stand-out track for me is In Fiction. It' takes an age
to build, but when it finally gets there it's the deepest, heaviest, most
crushing sound. Amazing pay-off.&lt;/p&gt;
&lt;h2 id="opeth--blackwater-park"&gt;Opeth – Blackwater Park&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for Blackwater Park" class="u-photo" height="150" src="/images/fair-use/blackwater-park.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://opeth.omerch.com/products/opeth-blackwater-park-cd"&gt;Omerch (buy)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/3CCkWrqhWcKU7qXK3ooEEo"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/blackwater-park/363715800"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While the first Opeth album I got into was Still Life (and Deliverance &amp;amp;
Damnation come close), my favourite remains Blackwater Park. Most of the music I
listen to avoids guitar solos and such. Opeth manage to do the reverse, and have
moments when every instrument seems to be executing a solo at the same time and
maintain coherence. I was originally hooked by The Drapery Falls, but the entire
album is exquisite. Give it a try, even if the thought of death-growl vocals is
off putting to you. It’s worth it.&lt;/p&gt;
&lt;h2 id="múm--finally-we-are-no-one"&gt;Múm – Finally We Are No One&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for Finally We Are No One" class="u-photo" height="150" src="/images/fair-use/finally-we-are-no-one.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://fatcatrecords.bandcamp.com/album/finally-we-are-no-one"&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/2XCcnYJJQXYoWm5oc20x9k"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/finally-we-are-no-one/285318218"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I was introduced to this me by an Italian I met in Tokyo (she introduced me to a
lot of really great music, I should thank her again). This album should sound
light and airy, but comes across and close and comforting, like being indoors by
a fire during a coastal storm. While I first listened to it in Tokyo I don’t
associate it with there. Its aesthetic is so strong that it tries to invoke
memories of a particular balance that I’ve not actually experienced beyond
listening to this album.&lt;/p&gt;
&lt;h2 id="the-avalanches--since-i-left-you"&gt;The Avalanches – Since I Left You&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for Since I Left You" class="u-photo" height="130" src="/images/fair-use/since-i-left-you.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/0CvU96jYCiNP4c9u8dWHoI"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/since-i-left-you/1450114829"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This album is famed for the extreme lengths the creators went to make it out of
samples. I was aware of it long before I listened to it (it was on in various
student pubs while I was doing my undergrad). I first really listened to it one
Summer I was living in Tokyo. It reminds me of the heat and humidity of the
summer there, but also the detachment I felt while I lived there. I moved to
Japan at relatively short notice after a difficult break up, and struggled to
find my footing for a long time after I arrived. This album reminds me of the
joy of summer and feeling at ease with myself and my freedom from my former life
and my culture. It probably marks the point at which I stopped identifying
myself as a nationality or a job, and started thinking of myself as a standard,
awkward mess of a person.&lt;/p&gt;
&lt;p&gt;This album reminds me that while we’re all a mishmash of different things, it’s
important to remember the difference between what we are, and what our
environments try to pigeonhole us as. Even more importantly, a mishmash can be a
beautiful and coherent whole.&lt;/p&gt;
&lt;h2 id="portico-quartet--memory-streams"&gt;Portico Quartet – Memory Streams&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for Memory Streams" class="u-photo" height="150" src="/images/fair-use/memory-streams.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://porticoquartet.bandcamp.com/album/memory-streams"&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/7rUuKsh6pJLcb44d1NBV81"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/memory-streams/1583551298"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is a fairly recent edition, and I find it difficult to categorize. The band
makes instrumental music with heavy use of &lt;a href="https://en.wikipedia.org/wiki/Hang_(instrument)"&gt;the Hang&lt;/a&gt; (a form of steel drum),
saxophones, and synths. I guess it's jazz, but I don't know enough about jazz to
really say. I bought the record as an impulse purchase shortly before the
pandemic lockdowns started. My son was still a baby then, so I was looking for
something to relax to. Due to the timing, it became the sound of the early
pandemic for me. Back then, it felt like the world was just the three of us.&lt;/p&gt;
&lt;h2 id="boards-of-canada--tomorrows-harvest"&gt;Boards of Canada – Tomorrow's Harvest&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for Tomorrow&amp;#x27;s Harvest" class="u-photo" height="150" src="/images/fair-use/tomorrows-harvest.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://boardsofcanada.bandcamp.com/album/tomorrows-harvest"&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/159ORixBSSemxiualv1Woj"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/tomorrows-harvest/641229267"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was an impulse buy, pretty much the day it came out in Japan. I used to
spend a lot of time trying to read the staff recommendations in the Tower
Records in Yodobashi Akihabara. Unfortunately that Tower Records has been gone
for a few years now. I had listened to Boards of Canada's earlier work, but
Tomorrow's Harvest blew my mind. The vintage sound and sparseness gives it a
brooding and foreboding feeling. This album is excellent to work to.&lt;/p&gt;
&lt;p&gt;I bought this album shortly before moving back to the UK from Japan, so it used
to remind me of that time. I've played it so often since that any nostalgia for
that time has long since gone, but I do still associate it with a change in
mindset that came with changing career from a physics academic to a software
engineer.&lt;/p&gt;
&lt;h2 id="the-devin-townsend-project---addicted"&gt;The Devin Townsend Project - Addicted&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for Addicted" class="u-photo" height="150" src="/images/fair-use/addicted.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://devintownsendofficial.bandcamp.com/album/addicted"&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/4vaF198cLJTlSZ49v1N1mk"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/addicted/1045214857"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Yes, another Devin Townsend album. This album was released just days before I
moved to Tokyo from the UK, and I listened to it a lot. I strongly associate it
with my first year in Japan. In particular with difficulty settling into the
time zone in the first few weeks, but also the fun little dance parties I had
with housemates and friends in the share house I was renting a room in. The
track &lt;em&gt;Bend it like Bender!&lt;/em&gt; was a favourite of one of my Italian friends. That
friendship ultimately led to me meeting my partner.&lt;/p&gt;
&lt;p&gt;Life is full of these incredibly unlikely little sequences of events with
important outcomes. What if that album came out a day later? I'd have been too
busy to buy it. What if I had chosen a different share house? What if the
aforementioned friend hadn't started a business with a friend of my (now)
partner and set up a Roman Holiday themed club night beneath a Fiat showroom
which we were both &amp;quot;encouraged&amp;quot; to attend to recoup some losses? What if my
partner and I hadn't been snacking on arancini next to each other and struck up
a conversation? What if...&lt;/p&gt;
&lt;h2 id="marconi-union---signals"&gt;Marconi Union - Signals&lt;/h2&gt;
&lt;p&gt;
    &lt;source type="image/webp" /&gt;
    &lt;source type="image/avif" /&gt;
    &lt;img alt="The album cover for Signals" class="u-photo" height="150" src="/images/fair-use/signals.jpeg" width="150" /&gt;
  &lt;/p&gt;
&lt;p&gt;Another relatively recent addition. I'd listened to some of their music before
and found it good to work to, so when Signals was released I bought it straight
away. It's an exceptional album. Tracks are repetitive, build slowly, and at
times feel meditative. There's a thrumming, almost dangerous quality to it.
Stand out tracks for me are Strata, and A Citizen's Dream. The latter I find
very affecting. It feels like longing, sadness, and hope, sometimes to the point
of tears when I'm actively listening to it. I have no idea why that is though!
Sometimes my reaction to music can't be explained. It's like it can be a key to
a random emotional door in my mind.&lt;/p&gt;
&lt;p&gt;I tend to react differently to others when I listen to music. I find quite
heavy, negative, loud music to be uplifting, and more pop-like music intended
to be uplifting will make me feel quite upset and angry.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://marconiunion.bandcamp.com/album/signals"&gt;Bandcamp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://open.spotify.com/album/1z6YgGKHAEJ9FIV93LP4SI?si=8b1a-X2oRJKqsCAjFSfkfQ"&gt;Spotify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://music.apple.com/gb/album/signals/1580742497"&gt;Apple&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Qubyte Codes</author><pubDate>Sat, 28 Dec 2024 16:25:00 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/blog/favourite-music</guid></item><item><title>Finally trying out the Android XML layout</title><link>https://whackylabs.com/kotlin/xml/android/2024/12/28/android-xml/</link><description>&lt;p&gt;So year after year since the dawn of the Android SDK back in 2009 I’ve been making the yearly resolution to try it out. But for one reason or the other I’ve always been putting it off. But not this year. Before the sun goes down for the last time for the year of 2024 let’s make our favorite photo app using the classic Android UI toolkit - The framework with no name - You know that thing where you write XML for layout. Let the fun begin!&lt;/p&gt;

&lt;h3 id="set-up"&gt;Set up&lt;/h3&gt;

&lt;p&gt;I’m going to use retrofit for networking with glide for loading images all connected together via kotlin coroutines.&lt;/p&gt;

&lt;p&gt;The first step is to install all the dependencies. Which from what I understand is now a 2 step job. First you list all the dependencies in the &lt;code class="language-plaintext highlighter-rouge"&gt;libs.versions.toml&lt;/code&gt; file&lt;/p&gt;

&lt;div class="language-toml highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nn"&gt;[versions]&lt;/span&gt;
&lt;span class="py"&gt;kotlin&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.0.0"&lt;/span&gt;
&lt;span class="py"&gt;coroutinesKtx&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.4.0"&lt;/span&gt;
&lt;span class="py"&gt;coroutines&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.9.0"&lt;/span&gt;
&lt;span class="py"&gt;gson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.11.0"&lt;/span&gt;
&lt;span class="py"&gt;retrofit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.11.0"&lt;/span&gt;
&lt;span class="py"&gt;okhttp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"4.12.0"&lt;/span&gt;
&lt;span class="py"&gt;glide&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"4.16.0"&lt;/span&gt;

&lt;span class="nn"&gt;[libraries]&lt;/span&gt;
&lt;span class="nn"&gt;retrofit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.squareup.retrofit2:retrofit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"retrofit"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;gson&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.google.code.gson:gson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"gson"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;retrofit-gson-converter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.squareup.retrofit2:converter-gson"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"retrofit"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;okhttp-logging-interceptor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.squareup.okhttp3:logging-interceptor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"okhttp"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;glide&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.github.bumptech.glide:glide"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"glide"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;glide-compiler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"com.github.bumptech.glide:compiler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"glide"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;kotlinx-coroutines-core&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.jetbrains.kotlinx:kotlinx-coroutines-core"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"coroutines"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;kotlinx-coroutines-android&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.jetbrains.kotlinx:kotlinx-coroutines-android"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"coroutines"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;androidx-viewmodel-ktx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"androidx.lifecycle:lifecycle-viewmodel-ktx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"coroutinesKtx"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;androidx-runtime-ktx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"androidx.lifecycle:lifecycle-runtime-ktx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"coroutinesKtx"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then you actually add them to the project by updating the &lt;code class="language-plaintext highlighter-rouge"&gt;build.gradle.kts&lt;/code&gt; file&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nf"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retrofit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gson&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;converter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gson&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;okhttp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;interceptor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glide&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;annotationProcessor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compiler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;androidx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;viewmodel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ktx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;androidx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ktx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kotlinx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coroutines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;implementation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;libs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;kotlinx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;coroutines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;android&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And finally, the most important step that I almost always forget. To add the &lt;strong&gt;INTERNET&lt;/strong&gt; permission in the &lt;code class="language-plaintext highlighter-rouge"&gt;AndroidManifest.xml&lt;/code&gt; file&lt;/p&gt;
&lt;div class="language-xml highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;manifest&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.INTERNET"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;uses-permission&lt;/span&gt; &lt;span class="na"&gt;android:name=&lt;/span&gt;&lt;span class="s"&gt;"android.permission.ACCESS_NETWORK_STATE"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;application&amp;gt;&lt;/span&gt;
        ...
    &lt;span class="nt"&gt;&amp;lt;/application&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/manifest&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id="fetching-data"&gt;Fetching data&lt;/h3&gt;
&lt;p&gt;Creating the network layer is pretty straightforward with Retrofit. You declare an interface of the API and let retrofit do the rest.&lt;/p&gt;

&lt;p&gt;For our needs, the API consists of just a single &lt;code class="language-plaintext highlighter-rouge"&gt;GET&lt;/code&gt; call to fetch a &lt;code class="language-plaintext highlighter-rouge"&gt;List&lt;/code&gt; of &lt;code class="language-plaintext highlighter-rouge"&gt;Photo&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;albumId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Serializable&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;API&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@GET&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"photos"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;suspend&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;NetworkService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;companion&lt;/span&gt; &lt;span class="k"&gt;object&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;API&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;loggingInterceptor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpLoggingInterceptor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;loggingInterceptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;level&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpLoggingInterceptor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Level&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;BODY&lt;/span&gt;

            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;okHttpClient&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OkHttpClient&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addInterceptor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;loggingInterceptor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;retrofit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Retrofit&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;baseUrl&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://jsonplaceholder.typicode.com/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConverterFactory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GsonConverterFactory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;okHttpClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;retrofit&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;API&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next piece of the puzzle is the &lt;code class="language-plaintext highlighter-rouge"&gt;PhotoListViewModel&lt;/code&gt; to map the network data into various UI states. Again pretty straightforward thanks to the beautiful magic of &lt;code class="language-plaintext highlighter-rouge"&gt;ViewModelScope&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;StateFlow&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PhotoListViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="kd"&gt;object&lt;/span&gt; &lt;span class="nc"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;_state&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_state&lt;/span&gt;

    &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;fetchPhotos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;viewModelScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt;
            &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;photos&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;NetworkService&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;api&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;body&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nf"&gt;emptyList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                &lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;message&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"Something went wrong"&lt;/span&gt;
                &lt;span class="n"&gt;_state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id="ui"&gt;UI&lt;/h3&gt;
&lt;p&gt;On the UI side we need 2 Activities, a &lt;code class="language-plaintext highlighter-rouge"&gt;MainActivity&lt;/code&gt; to render a grid of images and a &lt;code class="language-plaintext highlighter-rouge"&gt;DetailActivity&lt;/code&gt; to render the detailed image view.&lt;/p&gt;

&lt;p&gt;The basic structure of the &lt;code class="language-plaintext highlighter-rouge"&gt;MainActivity&lt;/code&gt; is to simply observes viewmodel changes and update the UI.&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;PhotoListViewModel&lt;/span&gt; &lt;span class="k"&gt;by&lt;/span&gt; &lt;span class="nf"&gt;viewModels&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;enableEdgeToEdge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;setContentView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activity_main&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;ViewCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnApplyWindowInsetsListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;insets&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;systemBars&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;insets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInsets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WindowInsetsCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPadding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;insets&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;lifecycleScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;repeatOnLifecycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Lifecycle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;STARTED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
                    &lt;span class="k"&gt;when&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                        &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;PhotoListViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;showLoading&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
                        &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;PhotoListViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Error&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;showError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="nc"&gt;PhotoListViewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Content&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;showPhotoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fetchPhotos&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;showLoading&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Toast&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Loading ..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LENGTH_LONG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;showError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Toast&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;makeText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;LENGTH_LONG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;showPhotoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// TODO show grid view&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The interesting bit is with building a photo grid. This requires a &lt;code class="language-plaintext highlighter-rouge"&gt;PhotoGridAdapter&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PhotoGridAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nc"&gt;BaseAdapter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getCount&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getItemId&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLong&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;getView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;convertView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewGroup&lt;/span&gt;&lt;span class="p"&gt;?):&lt;/span&gt; &lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;contentVw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
            &lt;span class="n"&gt;convertView&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nc"&gt;LayoutInflater&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;inflate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid_item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;imageVw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ImageView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contentVw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imageVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;textVw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TextView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;contentVw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;photo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nc"&gt;Glide&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;into&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;textVw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"-"&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;contentVw&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then use this adapter to provide UI data for the &lt;code class="language-plaintext highlighter-rouge"&gt;GridView&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;showPhotoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;gridVw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GridView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gridVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;photoGridAdapter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PhotoGridAdapter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;gridVw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;adapter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;photoGridAdapter&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Main Activity" src="https://whackylabs.com/assets/photoapp-android/mainactivity.png" /&gt;&lt;/p&gt;

&lt;p&gt;Next, when tapped on a photo tile we need to spawn &lt;code class="language-plaintext highlighter-rouge"&gt;DetailActivity&lt;/code&gt; and provide the selected &lt;code class="language-plaintext highlighter-rouge"&gt;Photo&lt;/code&gt; as argument.&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;

    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;showPhotoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;gridVw&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GridView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gridVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;gridVw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnItemClickListener&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;photo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;photos&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="nf"&gt;startActivity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Intent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;DetailsActivity&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nf"&gt;putExtra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"photo"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then finally the &lt;code class="language-plaintext highlighter-rouge"&gt;DetailActivity&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;DetailsActivity&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;AppCompatActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;enableEdgeToEdge&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;setContentView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;activity_details&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;ViewCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setOnApplyWindowInsetsListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;insets&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
            &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;systemBars&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;insets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getInsets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;WindowInsetsCompat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setPadding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;systemBars&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bottom&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;insets&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;photo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;intent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getSerializableExtra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"photo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nc"&gt;Photo&lt;/span&gt;

        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;imageVw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ImageView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;imageVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;textVw&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;TextView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findViewById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;textVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="nc"&gt;Glide&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;thumbnail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Glide&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;into&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imageVw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;textVw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"-"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Detail Activity" src="https://whackylabs.com/assets/photoapp-android/detailactivity.png" /&gt;&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;The classic Android development feels very much like UIKit for iOS. Most the concepts are 1:1 mapped. Kotlin is always very &lt;code class="language-plaintext highlighter-rouge"&gt;fun&lt;/code&gt; to work with.
The one thing I find weird about Android is the lack of in-house libraries for such a  basic thing like Networking, which is the core building block for almost all apps these days. And then how various companies have rolled out their own solution for building the data-event pipeline. Like with &lt;code class="language-plaintext highlighter-rouge"&gt;LiveData&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;StateFlow&lt;/code&gt; and kotlin coroutines leaving the developers to figure out the glue to make it all work.&lt;/p&gt;

&lt;p&gt;Before I discovered &lt;code class="language-plaintext highlighter-rouge"&gt;Glide&lt;/code&gt; I tried building my own &lt;code class="language-plaintext highlighter-rouge"&gt;ImageRepository&lt;/code&gt; to handle networking and data caching. And there too I felt the tools offered by Android were very primitive compared to say Apple’s &lt;code class="language-plaintext highlighter-rouge"&gt;URLSession&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Also before falling back to retrofit, I was trying out ktor for networking. Personally I like ktor as it seems more flexible and more in line with other networking libraries I’m used to.&lt;/p&gt;

&lt;p&gt;And finally the XML based layout feels very nice and robust. I like the code-design split view.&lt;/p&gt;

&lt;p&gt;The hardest part about Android development is dealing with the unknowns. Like I ran into a runtime crash when running the app on my device with is running on Android 11/SDK 30 while on the emulator I was using Android 13/SDK 34. But I guess that is just a day in life for a developer, you know the classic “Works on my machine” phenomenon.&lt;/p&gt;

&lt;p&gt;The code from this experiment is sitting right next to all other experiments. &lt;a href="https://github.com/chunkyguy/PhotoApp/tree/master/android"&gt;github.com/chunkyguy/PhotoApp/android&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="references"&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://developer.android.com/topic/libraries/architecture/coroutines"&gt;https://developer.android.com/topic/libraries/architecture/coroutines&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://mubaraknative.medium.com/making-an-http-request-using-ktor-client-a87c2593ed25"&gt;https://mubaraknative.medium.com/making-an-http-request-using-ktor-client-a87c2593ed25&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://developer.android.com/reference/android/widget/GridView"&gt;https://developer.android.com/reference/android/widget/GridView&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://developer.android.com/courses/pathways/android-basics-compose-unit-5-pathway-1"&gt;https://developer.android.com/courses/pathways/android-basics-compose-unit-5-pathway-1&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ktor.io/docs/welcome.html"&gt;https://ktor.io/docs/welcome.html&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://jsonplaceholder.typicode.com/photos"&gt;https://jsonplaceholder.typicode.com/photos&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Yps0G5M4ilA"&gt;https://www.youtube.com/watch?v=Yps0G5M4ilA&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Sat, 28 Dec 2024 11:55:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/kotlin/xml/android/2024/12/28/android-xml/</guid></item><item><title>The $500 Question - How Media Framing Shapes Our Reality</title><link>https://lagomor.ph/2024/12/the-500-question-how-media-framing-shapes-our-reality/</link><description>&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;In this season of giving, what are we to make of a billionaire with a soft spot for striving graduates who draws a hard line on being present for the pomp and circumstance, no matter the circumstances?&amp;rdquo; - &lt;a href="https://www.nytimes.com/2024/12/25/your-money/umass-dartmouth-billionaire-charity.html" rel="noopener noreferrer" target="_blank"&gt;New York Times&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The modern media apparatus doesn&amp;rsquo;t just report news - it manufactures reality through carefully constructed narratives that shape how we process and respond to events. This process has become so refined that different outlets can take the same event and create entirely separate realities, each designed to reinforce specific worldviews and emotional responses in their target audiences.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Sat, 28 Dec 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/2024/12/the-500-question-how-media-framing-shapes-our-reality/</guid></item><item><title>symbol-overlay-mc now on MELPA</title><link>https://xenodium.com/symbol-overlay-mc-now-on-melpa</link><description>&lt;p&gt;Some time ago, I wrote about &lt;a href="https://xenodium.com/its-all-up-for-grabs-and-it-compounds/"&gt;adding multiple cursor support to symbol-overlay&lt;/a&gt; by gluing the two packages together. If you're keen to learn how to bend Emacs to your way, have a look at that post.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/its-all-up-for-grabs-and-it-compounds/symbol-overlay-meets-multiple-cursors.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Later on, in a &amp;quot;Multiple cursors - how and why?&amp;quot; &lt;a href="https://www.reddit.com/r/emacs/comments/1h321x2/multiple_cursors_how_and_why"&gt;Reddit post&lt;/a&gt;, I showcased the overlay usage. &lt;a href="https://www.mavit.org.uk"&gt;Peter Oliver&lt;/a&gt; asked &lt;a href="https://www.reddit.com/r/emacs/comments/1h321x2/comment/m06x676/?utm_source=share&amp;amp;utm_medium=web3x&amp;amp;utm_name=web3xcss&amp;amp;utm_term=1&amp;amp;utm_content=share_button"&gt;if the package could be submitted to MELPA&lt;/a&gt;. To my delight, he also volunteered to &lt;a href="https://github.com/melpa/melpa/pull/9267"&gt;take on the submission&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, I had failed to check back on the MELPA submission until today. I'm happy to report that, thanks to Peter, &lt;code&gt;symbol-overlay-mc&lt;/code&gt; &lt;a href="https://melpa.org/#/symbol-overlay-mc"&gt;is now on MELPA&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy symbol-overlay-multiple-cursors editing! ;)&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Sat, 28 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/symbol-overlay-mc-now-on-melpa</guid></item><item><title>Emulating 6502</title><link>https://zserge.com/posts/6502/</link><description>Last year, I noticed that routine programming no longer brought me the same joy it did decades ago. So, my New Year&amp;rsquo;s resolution was to engage in more &amp;ldquo;useless&amp;rdquo; programming &amp;ndash; coding small, fun projects without any specific end goal in mind. Of all the possible topics, the one that captivated me throughout the year was retrocomputing.
I didn’t have a computer until I was 14, but I remember seeing grey 8-bit machines at school or friends' houses.</description><author>zserge's blog</author><pubDate>Sat, 28 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zserge.com/posts/6502/</guid></item><item><title>Better AI LLM assistance for Svelte 5 and SvelteKit</title><link>https://khromov.se/getting-better-ai-llm-assistance-for-svelte-5-and-sveltekit/</link><description>&lt;p&gt;With the release of Svelte 5 and its new runes syntax, many developers are wondering how to get reliable AI assistance that understands the latest features in Svelte 5 working with ChatGPT, Claude, Continue, Cursor and other tools. Looking for smaller, distilled LLM presets? Click here! The challenge with AI and new frameworks When a new major framework version is released, AI tools often struggle to provide accurate code samples since they were trained on older documentation. This creates two main problems: The official solution: llms.txt As part of Advent of Svelte 2024, Svelte introduced official support for AI tools [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://khromov.se/getting-better-ai-llm-assistance-for-svelte-5-and-sveltekit/"&gt;Better AI LLM assistance for Svelte 5 and SvelteKit&lt;/a&gt; appeared first on &lt;a href="https://khromov.se"&gt;Stanislav Khromov&lt;/a&gt;.&lt;/p&gt;</description><author>Stanislav Khromov</author><pubDate>Sat, 28 Dec 2024 00:41:02 GMT</pubDate><guid isPermaLink="true">https://khromov.se/getting-better-ai-llm-assistance-for-svelte-5-and-sveltekit/</guid></item><item><title>Starting Tests from Zero</title><link>https://dev.to/johntellsall/starting-tests-from-zero-2fml</link><description>&lt;p&gt;BEER&lt;/p&gt;

&lt;p&gt;Often developers and businesses can't see the value that testing gives. Devs see it as a "tax", an extra thing that slows them down from doing their "real job". Businesses go into magical thinking mode, that Devs are always creating flawless features that deliver whatever the Business thinks it does and fits into the system.&lt;/p&gt;

&lt;p&gt;Tests don't &lt;em&gt;slow down&lt;/em&gt; development, then &lt;em&gt;pull the development forward&lt;/em&gt;. Writing "happy path" Unit Tests shows Devs what to work on next. Investing in high level End to End tests gives the Business confidence the system is working reliably and new changes aren't dangerous.&lt;/p&gt;

&lt;p&gt;Here's how to get started with tests if you have non. &lt;/p&gt;

&lt;h1&gt;
  
  
  Top-down vs Bottom-up: two different Audiences
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Write one or more end-to-end tests (E2E). This gives confidence that the entire app is solid, at the cost of being slow and not very detailed. The main benefit is for the Business: they can see and understand the results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back in the day, for a shopping site, our E2E test was "create user, add item to cart, checkout, validate there's a total cost" kind of thing. It was slow but had really obvious business value. Nowadays you'd do something like Playwright for this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write lots of "happy path" unit tests, generally one per non-obvious function. These are &lt;em&gt;fast&lt;/em&gt; and give a lot of info, but for a tiny scope: one function. Generally any time I have to think, I write a test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also write a unit test per bug, even if it's just "this doesn't work" or "expect some sort of Exception to happen here". Tests can be vague if that gives you actionable feedback as a developer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;publish Test Coverage! This is great as you and your team -- and more importantly the Business -- can see this number and how it changes over time. New code makes number go down? Write tests for the new code (or old code, if that makes sense for your team). Bug happens? Write test -- number goes up -- then fix the bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The overall goal is getting into a Value Feedback Cycle. For the Business audience, they want confidence the app is functioning as expected, that a new deploy won't bring down the site. For the Dev audience, they want fast actionable feedback for them to complete the features. The two types of tests: End to End and Unit Tests, give this feedback to the two audiences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced&lt;/strong&gt;: unit tests don't cover much code. Consider adding API-level tests like with Postman. These are cheaper in that a single test checks more code, even though at a less detailed level. They're also fun for the Business and Devs to see because a single API test makes the Test Coverage go up more than from a single Unit Test.&lt;/p&gt;</description><author>DEV Community: John Mitchell</author><pubDate>Fri, 27 Dec 2024 19:19:42 GMT</pubDate><guid isPermaLink="true">https://dev.to/johntellsall/starting-tests-from-zero-2fml</guid></item><item><title>Starting Tests from Zero</title><link>https://dev.to/johntellsall/starting-tests-from-zero-2kha</link><description>&lt;p&gt;Often developers and businesses can't see the value that testing gives. Devs see it as a "tax", an extra thing that slows them down from doing their "real job". Businesses go into magical thinking mode, that Devs are always creating flawless features that deliver whatever the Business thinks it does and fits into the system.&lt;/p&gt;

&lt;p&gt;Tests don't &lt;em&gt;slow down&lt;/em&gt; development, then &lt;em&gt;pull the development forward&lt;/em&gt;. Writing "happy path" Unit Tests shows Devs what to work on next. Investing in high level End to End tests gives the Business confidence the system is working reliably and new changes aren't dangerous.&lt;/p&gt;

&lt;p&gt;Here's how to get started with tests if you have non. &lt;/p&gt;

&lt;h2&gt;
  
  
  Top-down vs Bottom-up: two different Audiences
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Write one or more end-to-end tests (E2E). This gives confidence that the entire app is solid, at the cost of being slow and not very detailed. The main benefit is for the Business: they can see and understand the results.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Back in the day, for a shopping site, our E2E test was "create user, add item to cart, checkout, validate there's a total cost" kind of thing. It was slow but had really obvious business value. Nowadays you'd do something like Playwright for this.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Write lots of "happy path" unit tests, generally one per non-obvious function. These are &lt;em&gt;fast&lt;/em&gt; and give a lot of info, but for a tiny scope: one function. Generally any time I have to think, I write a test.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also write a unit test per bug, even if it's just "this doesn't work" or "expect some sort of Exception to happen here". Tests can be vague if that gives you actionable feedback as a developer.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;publish Test Coverage! This is great as you and your team -- and more importantly the Business -- can see this number and how it changes over time. New code makes number go down? Write tests for the new code (or old code, if that makes sense for your team). Bug happens? Write test -- number goes up -- then fix the bug.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The overall goal is getting into a Value Feedback Cycle. For the Business audience, they want confidence the app is functioning as expected, that a new deploy won't bring down the site. For the Dev audience, they want fast actionable feedback for them to complete the features. The two types of tests: End to End and Unit Tests, give this feedback to the two audiences.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Advanced&lt;/strong&gt;: unit tests don't cover much code. Consider adding API-level tests like with Postman. These are cheaper in that a single test checks more code, even though at a less detailed level. They're also fun for the Business and Devs to see because a single API test makes the Test Coverage go up more than from a single Unit Test.&lt;/p&gt;</description><author>DEV Community: John Mitchell</author><pubDate>Fri, 27 Dec 2024 19:02:24 GMT</pubDate><guid isPermaLink="true">https://dev.to/johntellsall/starting-tests-from-zero-2kha</guid></item><item><title>Work 2.0 - the interruptible programmer</title><link>https://prashamhtrivedi.in/interruptible_programmer/</link><description>This is a good read from Steves String. I discovered this blog from a youtube channel called Internet of Bugs. The post completely changes the way we think about productivity and work, especially when your are at the other end of your 30s.
The post discusses about how it&amp;rsquo;s not feasible to work for long hours without any interruption. It&amp;rsquo;s not just about the physical health, but also about the mental health.</description><author>Prasham H Trivedi</author><pubDate>Fri, 27 Dec 2024 17:30:52 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/interruptible_programmer/</guid></item><item><title>Capturing slack messages directly into Emacs orgmode inbox</title><link>https://srijan.ch/capturing-slack-messages-directly-into-emacs-orgmode-inbox</link><description>Learn how to seamlessly capture Slack messages into your Emacs Orgmode (GTD) inbox using a custom browser userscript.</description><author>Srijan Choudhary, all posts</author><pubDate>Fri, 27 Dec 2024 13:50:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/capturing-slack-messages-directly-into-emacs-orgmode-inbox</guid></item><item><title>Ghostty on Ubuntu: First Impressions and How to Install</title><link>https://www.mikekasberg.com/blog/2024/12/26/ghostty-on-ubuntu.html</link><description>&lt;p&gt;&lt;a href="https://ghostty.org"&gt;Ghostty&lt;/a&gt; came out today! I’ve been following the
development on &lt;a href="https://mitchellh.com/writing"&gt;Mitchell Hashimoto’s Blog&lt;/a&gt; for a
while, and I was excited to give the terminal a try.&lt;/p&gt;

&lt;h2 id="first-impressions"&gt;First Impressions&lt;/h2&gt;

&lt;p&gt;Ghostty is great! My first impression was that it looked exactly like Gnome
Terminal, with a slightly different color scheme. That’s not a criticism, that’s
actually high praise! Mitchell has written &lt;a href="https://mitchellh.com/writing/ghostty-is-coming"&gt;on his
blog&lt;/a&gt; about the importance of
Ghostty being native while also being cross-platform. That is to say, although
it works on both macOS and Linux, it uses native windows, menu bars, and tabs.
After trying Ghostty for a little bit, I have to say I agree, and he nailed it!&lt;/p&gt;

&lt;p&gt;&lt;img alt="Ghostty terminal on Ubuntu" src="https://www.mikekasberg.com/images/ghostty-on-ubuntu/ghostty.png" /&gt;&lt;/p&gt;

&lt;p&gt;I was really pleased to see that everything just worked out of the box (and I
know Mitchell’s put in a lot of effort here too). I’ve been using Gnome Terminal
as my daily driver on Ubuntu for more than ten years, and I knew within a few
minutes of trying Ghostty that I’d be using Ghostty as my daily driver going
forward. I’ve briefly tried other terminals before, like Kitty or Alacritty, but
I always came back to Gnome Terminal – specifically because I don’t like the
non-native look and feel of others. I make heavy use of native tabs, so the
non-native tabs (or complete lack of tabs) and non-native UI of other Terminals
kept me using the default Gnome Terminal, despite knowing the others were
faster. With Ghostty, I can have my cake and eat it too!&lt;/p&gt;

&lt;p&gt;Ghostty is simple (it really does look very similar to Gnome Terminal), but it’s
fast and has advanced configuration if you need it. I haven’t needed it yet,
though I plan to experiment with different themes in the near future. I’m
finishing this blog post in vim in Ghostty, and so far, Ghostty has been
everything I hoped it would be. I highly recommend it! Mitchell, congrats on a
great Terminal and a great launch! I appreciate all your work (and the work of
the rest of the community)!&lt;/p&gt;

&lt;h2 id="how-to-install-ghostty-on-ubuntu"&gt;How to Install Ghostty on Ubuntu&lt;/h2&gt;

&lt;div class="message"&gt;
I'm working on an &lt;b class="not-prose"&gt;&lt;a href="https://github.com/mkasberg/ghostty-ubuntu"&gt;
unofficial Ubuntu package for Ghostty&lt;/a&gt;&lt;/b&gt;! This should be easier to install
than building from source as outlined below. Follow the instructions on
GitHub, and open an issue there if you run into problems!
&lt;/div&gt;

&lt;p&gt;There’s no official (or unofficial) Ubuntu package yet, so the only way to get
Ghostty running on Ubuntu right now is to build it yourself. Fortunately, this
isn’t too hard! The build instructions are documented in the Ghostty docs, but
I’ll lay out some step-by-step instructions for Ubuntu here.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;You’ll need Zig 0.13. There are several ways to install this; I installed it
by downloading a binary release. Download the &lt;code class="language-plaintext highlighter-rouge"&gt;.tar.xz&lt;/code&gt; file for your
architecture from the &lt;a href="https://ziglang.org/download/"&gt;Zig Downloads&lt;/a&gt; page.
You probably want the x86_64 version. Make sure you get version 0.13 for Linux.&lt;/li&gt;
  &lt;li&gt;Extract the &lt;code class="language-plaintext highlighter-rouge"&gt;.tar.xz&lt;/code&gt; file to your &lt;code class="language-plaintext highlighter-rouge"&gt;/opt&lt;/code&gt; directory. (Any directory would be
fine, &lt;code class="language-plaintext highlighter-rouge"&gt;/opt&lt;/code&gt; is conventional for stuff like this and is what I chose.)
    &lt;div class="language-plaintext terminal highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;sudo tar -xf zig-linux-x86_64-0.13.0.tar.xz -C /opt
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Make sure you have the required dependencies to compile Ghostty, as
documented in the &lt;a href="https://ghostty.org/docs/install/build#linux-installation-tips"&gt;Ghostty docs&lt;/a&gt;.
    &lt;div class="language-plaintext terminal highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;sudo apt install libgtk-4-dev libadwaita-1-dev git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Download Ghostty. The easiest way is to just clone the git repo. Then you can
check out a specific tag, or just build main.
    &lt;div class="language-plaintext terminal highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;git clone https://github.com/ghostty-org/ghostty.git
cd ghostty
git checkout v1.0.0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Build Ghostty. Again, following the &lt;a href="https://ghostty.org/docs/install/build#installation-directory"&gt;docs&lt;/a&gt;.
    &lt;div class="language-plaintext terminal highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;/opt/zig-linux-x86_64-0.13.0/zig build -p $HOME/.local -Doptimize=ReleaseFast
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;    &lt;/div&gt;
  &lt;/li&gt;
  &lt;li&gt;Run &lt;code class="language-plaintext highlighter-rouge"&gt;update-desktop-database&lt;/code&gt; to refresh the application list so you get
the Ghostty icon in your launcher. (If that command fails, try it with &lt;code class="language-plaintext highlighter-rouge"&gt;sudo&lt;/code&gt;.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it! By default, &lt;code class="language-plaintext highlighter-rouge"&gt;.local/bin&lt;/code&gt; should be in your &lt;code class="language-plaintext highlighter-rouge"&gt;$PATH&lt;/code&gt;, so the &lt;code class="language-plaintext highlighter-rouge"&gt;ghostty&lt;/code&gt;
command should now be available. The Ghostty build process should also have
installed a launcher file to &lt;code class="language-plaintext highlighter-rouge"&gt;$HOME/.local/share/applications&lt;/code&gt;, so you should be
able to find Ghostty in your normal application launcher menu without any extra
effort!&lt;/p&gt;

&lt;p&gt;For the time being, I think this is the easiest way to install Ghostty on
Ubuntu. It will take some time for Ghostty to make it into the official Debian
or Ubuntu repositories, but I hope to see some other installation options like
Flatpak, Snap, Linux binaries, or Homebrew (on Linux) become available in the
coming weeks.&lt;/p&gt;

&lt;p&gt;I’m looking forward to using Ghostty as my default shell, and I hope this helps
you get it set up on your own machine!&lt;/p&gt;</description><author>Mike Kasberg's Blog</author><pubDate>Fri, 27 Dec 2024 05:30:00 GMT</pubDate><guid isPermaLink="true">https://www.mikekasberg.com/blog/2024/12/26/ghostty-on-ubuntu.html</guid></item><item><title>Unoffice hours</title><link>https://robkohr.com/articles/unoffice-hours</link><description>Unoffice hours</description><author>RobKohr's Blog</author><pubDate>Fri, 27 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/unoffice-hours</guid></item><item><title>Jest ESM Issues: A Meta Tech Cautionary Tale</title><link>https://prashamhtrivedi.in/post/jest_esm/</link><description>Jest, ESM, and the Meta Tech Conundrum: A Cautionary Tale You know that moment when you&amp;rsquo;re deep in your TypeScript project, everything&amp;rsquo;s going smooth, and then you import a simple CSV parsing library? Yeah, that&amp;rsquo;s when Jest decides to remind you about Facebook&amp;rsquo;s - sorry, Meta&amp;rsquo;s - opinions on JavaScript modules.
Here&amp;rsquo;s what happened in my project today: A simple import from csv-parse/lib/sync led me down a rabbit hole of Jest configuration hell.</description><author>Prasham H Trivedi</author><pubDate>Fri, 27 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/post/jest_esm/</guid></item><item><title>TDD and the Zero-Defects Myth</title><link>https://sklivvz.com/posts/tdd-and-the-zero-defects-myth</link><description>&lt;p&gt;If you’ve ever heard someone claim that Test-Driven Development leads to zero-defects code, congratulations, you’ve encountered one of software development’s most enduring myths.&lt;/p&gt;

&lt;p&gt;&lt;img alt="kent's claim" src="https://res.cloudinary.com/dafktn5ur/image/upload/v1772965875/sklivvz-com/sklivvz-com/Iz1hhqQ.png" /&gt;&lt;/p&gt;

&lt;p&gt;It’s an alluring idea. Write tests first, and your code becomes bulletproof. But like many things in life, the reality is far messier. TDD isn’t a golden ticket to perfection, nor should it be. Here’s why the idea of zero defects is both unrealistic and, in many cases, extremely counterproductive.&lt;/p&gt;

&lt;blockquote&gt;
Edit: To be fair, Kent Beck is not perpetuating the myth, but in my opinion a lot of people will hear what they want to hear. The myth does exist, but I certainly don't blame it on Kent
&lt;/blockquote&gt;

&lt;h2&gt;Do we even need zero defects?&lt;/h2&gt;

&lt;p&gt;When was the last time a minor bug caused irreparable harm? For most software, the occasional defect is an acceptable trade-off for delivering faster and focusing on iterating on features that matter to the user. The startup world thrives on this ethos. &lt;a href="https://x.com/frontlinepbs/status/1057054323501395969"&gt;Move fast and break things&lt;/a&gt;, as Facebook famously said. Bugs are annoying, but users often care more about whether your app solves their problems or delights them than whether it’s abstractly defect-free.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gettingreal.37signals.com/ch04_Perfection_Not_Required.php"&gt;Perfection is the enemy of done&lt;/a&gt;. The pursuit of zero defects often descends into diminishing returns. Time and effort are burned polishing code that could have shipped weeks ago. Worse, the obsession with eliminating every defect can create a false sense of productivity. Are you building valuable features or just proving that your tests pass? &lt;/p&gt;

&lt;h2&gt;TDD and the real world&lt;/h2&gt;

&lt;p&gt;TDD works well in controlled environments with clear, deterministic problems. For example, I've used it fruitfully to write my &lt;a href="https://sklivvz.com/posts/z80"&gt;Z80 emulator&lt;/a&gt;. If you can define the inputs, predict the outputs, and cover edge cases, TDD is like a superpower. But the real world is rarely that tidy. &lt;/p&gt;

&lt;p&gt;Security is one of the hardest areas to test because it involves knowing what you don't know and not merely verifying what you do know. Tests might ensure queries are parameterized correctly, but can they anticipate every creative payload an attacker might send? Security is a moving target. Exhaustive testing requires a combination of manual review, automated tools, and red-team exercises. These go well beyond what TDD can provide. Either "zero defects" is flatly wrong or  "a gaping security hole" does not count as a "defect" in &lt;del&gt;Kent Beck's&lt;/del&gt; TDD's definition. Either way, it's not a message that should be boosted.&lt;/p&gt;

&lt;p&gt;When was the last time a test suite told you whether users enjoyed using your app? Usability issues are almost impossible to define as failures in the TDD sense because they rely on subjective human experience. Tests might confirm a button performs the right action when clicked, but they can’t tell you whether users can actually find it. The only way to evaluate usability is to test with real people through usability studies or A/B testing. TDD cannot simulate a frustrated user abandoning your app because they couldn’t figure out how to use it. This is an example as to why the number of defects is red herring: if you make a button hard to find, people won't press it so &lt;em&gt;it doesn't matter&lt;/em&gt; whether it works.&lt;/p&gt;

&lt;p&gt;Performance testing is another area where TDD gives zero meaningful coverage. Performance isn’t about whether something works. It’s about whether it works well under real-world conditions. A checkout function might process orders correctly in tests, but what happens when a thousand users try to check out simultaneously on Black Friday? The best proven way to evaluate performance in real-world scenarios isn’t to rely on synthetic benchmarks. It’s to use canary deployments. Large-scale systems like those at Stack Overflow &lt;a href="https://nickcraver.com/blog/2016/05/03/stack-overflow-how-we-do-deployment-2016-edition/?r=1#tiers"&gt;embrace this practice&lt;/a&gt;. Rolling out changes to a small subset of users in production allows you to measure actual impact on latency, throughput, and stability in a controlled way. Canary deployments reveal insights synthetic tests miss, like how systems perform under realistic user behavior and workloads, including cases where your pretty, "defect free" code dies in production because it's not suitable for the high-load scenario where it's run.&lt;/p&gt;

&lt;h2&gt;Tests are bad specifications&lt;/h2&gt;

&lt;p&gt;One of TDD’s core principles is that tests double as specifications. The idea is seductive. Write the tests first, and you define your system’s behavior. But how do you know you’ve written enough tests?&lt;/p&gt;

&lt;p&gt;Take a simple example. You’re implementing the sine function. Is one test enough?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;assert sin(0) == 0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Clearly not. How about these?&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;assert sin(0) == 0
assert sin(pi/2) == 1
assert sin(-pi/2) == -1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Still not enough. No matter how many test cases you write, it’s trivial to create a broken implementation of &lt;code&gt;sin(x)&lt;/code&gt; that passes them all. Writing enough tests to truly define a function’s behavior isn’t just hard. It’s often impossible. The way it's done in TDD is by often by basically cheating: write a few tests, write the &lt;em&gt;obviously right code&lt;/em&gt; you know in advance, and, lo and behold, it passes the tests you selected. Then you "decide" the tests are arbitrarily enough.&lt;/p&gt;

&lt;p&gt;Of course, in the real world, we find extensive use of this "technique" and this spoils the "ideal world" scenario and this also makes it clear to you why "&lt;a href="https://en.wikipedia.org/wiki/Bugzilla#Zarro_Boogs"&gt;zarro boogs&lt;/a&gt;" is a bit of a joke.&lt;/p&gt;

&lt;h2&gt;Test-induced damage&lt;/h2&gt;

&lt;p&gt;Even ideal world TDD isn’t without its downsides. One of the most notorious is &lt;a href="https://dhh.dk/2014/test-induced-design-damage.html"&gt;test-induced design damage&lt;/a&gt;. This happens when the need to even have tests distorts the design of your code. You might inject mocks into every crevice of your system to satisfy unit tests, creating brittle, over-engineered structures that are harder to maintain. Or you might focus so heavily on making tests pass that you lose sight of what your code is actually supposed to do. In the worst cases, the tests become the goal, not the guide. You end up with a green test suite and a broken product. &lt;/p&gt;

&lt;p&gt;That’s ironic for a methodology meant to improve quality.&lt;/p&gt;

&lt;h2&gt;Counting bugs is meaningless&lt;/h2&gt;

&lt;p&gt;TDD advocates often focus on reducing the number of bugs as a key metric. But not all bugs are created equal. You can ship software with a hundred tiny bugs no one notices and be fine. One critical bug, though, can sink your system.&lt;/p&gt;

&lt;p&gt;Consider the Amazon AWS S3 outage in 2017, caused by a &lt;a href="https://aws.amazon.com/message/41926/"&gt;simple human error in a command-line operation&lt;/a&gt;. This wasn’t a coding bug. It was a process flaw, and a failure to anticipate bad input, that brought down a huge chunk of the internet. TDD would not realistically have caught that because it's nearly impossible to predict all ways in which a system might be misused. Focusing solely on the number of bugs is like counting raindrops in a storm while ignoring the flood about to hit. You might have your mythical "zero defects" and bring down the internet.&lt;/p&gt;

&lt;h2&gt;Resilience is the goal&lt;/h2&gt;

&lt;p&gt;TDD is a valuable tool, but like any tool, it has its limits and a specific domain of applicability. The real world is messy, unpredictable, and full of problems that cannot be solved by writing tests first. Chasing zero defects is almost always a distraction from what really matters. Building resilient systems, solving real problems, and delivering value to users.&lt;/p&gt;

&lt;p&gt;Instead of obsessing over perfection, focus on pragmatism. Use TDD where and if it helps, but don’t treat it as a panacea or spread misinformation about its applicability. Users don’t care how you wrote your code. They care that it works. Sometimes, that means shipping something imperfect but functional, learning from feedback, and improving as you go.&lt;/p&gt;

&lt;p&gt;Perfection was never part of the job description.&lt;/p&gt;</description><author>Sklivvz.com</author><pubDate>Fri, 27 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://sklivvz.com/posts/tdd-and-the-zero-defects-myth</guid></item><item><title>Certification Exam Resources</title><link>https://www.vladsiv.com/posts/certification-exam-resources</link><description>I've put together a collection of resources I've used while preparing for various certification exams - covering everything from AWS to Databricks. The focus is mainly on data engineering and machine learning topics.</description><author>vladsiv</author><pubDate>Fri, 27 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.vladsiv.com/posts/certification-exam-resources</guid></item><item><title>The MMT Perspective on Crypto</title><link>https://bytepawn.com/mmt-crypto.html</link><description>&lt;p&gt;From an MMT (chartalist) perspective, cryptocurrencies lack sovereign backing and fail to establish the enforced demand necessary to function as true currency. Because they rely solely on speculative sentiment rather than legal mandates (like taxation), their values are unsustainable in the long run and risk ultimately collapsing to zero.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/constitution-taxes.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 27 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/mmt-crypto.html</guid></item><item><title>Vandring i Hågadalen</title><link>https://liza.io/vandring-i-h%C3%A5gadalen/</link><description>&lt;p&gt;Idag visade en vän mig Hågadalen. Det är så härligt att ha så vacker natur på promenadavstånd från mitt hem. Vi vandrade dit från hans lägenhet efter att ha druckit lite alkoholfri glögg. Jag tog med fika, och han hade med sig utsökta lunchsmörgåsar.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Thu, 26 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/vandring-i-h%C3%A5gadalen/</guid></item><item><title>Programming Lewis Carroll's *Memoria Technica*</title><link>https://ztoz.blog/posts/memoria_technica/</link><description>&lt;p&gt;Charles Dodgson (pen name Lewis Carroll) had difficulty remembering numbers, such as dates. He developed a cipher to help him remember numbers by embedding them in couplets or phrases. For example, the couplet &amp;ldquo;Brass trumpet and brazen bassoon, will speedily mark you a tune&amp;rdquo; encodes the specific gravity of brass (8.39) in the last four consonants: &lt;code&gt;r k t n&lt;/code&gt; (y is treated as a vowel). In this article, we describe the cipher, present online tools for encoding and decoding, discuss how we implemented the algorithms in TypeScript, and the cipher&amp;rsquo;s relevancy to steganography.&lt;/p&gt;
&lt;h2 id="memoria-technica"&gt;&lt;em&gt;Memoria Technica&lt;/em&gt;&lt;/h2&gt;
&lt;p&gt;The cipher encodes plaintext&amp;rsquo;s of sequences of digits. Using the cipher, consonants (except &amp;lsquo;y&amp;rsquo;) are mapped to digits. Vowels, punctuation, and &amp;lsquo;y&amp;rsquo; are ignored and thus can be freely added to the ciphertext The mapping follows the figure:&lt;/p&gt;
&lt;!-- | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 0 |
|---|---|---|---|---|---|---|---|---|---|
| b | d | t | f | l | s | p | h | n | z |
| c | w | j | q | v | x | m | k | g | r | --&gt;
&lt;figure&gt;&lt;img src="digit_mapping.png" /&gt;&lt;figcaption&gt;
            &lt;h4&gt;Mapping of consonants and digits&lt;/h4&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;In a three-page pamphlet published by Carroll in 1878, he describes how the digits and their associated letters came to be:&lt;/p&gt;
&lt;ol&gt;
 &lt;li value="1"&gt;"b" and "c", the first two consonants in the Alphabet.&lt;/li&gt;
 &lt;li&gt;"d" from "duo"; "w" from "two".&lt;/li&gt;
 &lt;li&gt;"t" from "tres"; the other may wait awhile.&lt;/li&gt;
 &lt;li&gt;"f" from four; "q" from "quatuor".&lt;/li&gt;
 &lt;li&gt;"l" and "v", because "L" and "V" are the Roman symbols for "fifty" and "five".&lt;/li&gt;
 &lt;li&gt;"s" and "x", from "six".&lt;/li&gt;
 &lt;li&gt;"p" and "m", from "septem".&lt;/li&gt;
 &lt;li&gt;"h" from "huit"; and "k" from the Greek "okto".&lt;/li&gt;
 &lt;li&gt;"n" from "nine"; and "g" because it is so like "9".&lt;/li&gt;
 &lt;li value="0"&gt;"z" and "r", from "zero".&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The letter &amp;ldquo;j&amp;rdquo; is associated with 3 because it is left-over.&lt;/p&gt;
&lt;p&gt;Decoding ciphertext involves extracting the consonants from a message and finding the mapped integer. Vowels, y, and punctuation are ignored.&lt;/p&gt;
&lt;p&gt;Encoding an integer involves choosing one of two consonants for each digit and inserting vowels, y, and punctuation to create valid words.&lt;/p&gt;
&lt;p&gt;Decoding may also involve &amp;ldquo;meta&amp;rdquo; information. In the pamphlet, Carroll provides example couplets for encoding the specific gravities of various metals. The decoder will need to know that only the last four consonants are relevant.&lt;/p&gt;
&lt;h3 id="example"&gt;Example&lt;/h3&gt;
&lt;p&gt;Given the ciphertext &lt;code&gt;found&lt;/code&gt;, we can first filter to the consonants: &lt;code&gt;fnd&lt;/code&gt;. f is 4, n is 9, and d is 2, so the plaintext is &lt;code&gt;492&lt;/code&gt;. (What was found in 492? Christopher Columbus discovered America in 1492 and Dodgson believed the leading 1 could be assumed.)&lt;/p&gt;
&lt;p&gt;Given the plaintext &lt;code&gt;492&lt;/code&gt;, we are interested in words that include the consonants f or q, n or g, and d or w, in that order, with vowels, &amp;lsquo;y&amp;rsquo;, or punctuation in-between. Using the second edition Scrabble dictionary, we find 15 single words that match these constraints:&lt;/p&gt;
&lt;table&gt;
	&lt;tbody&gt;
		&lt;tr&gt;
			&lt;td&gt;fend&lt;/td&gt;&lt;td&gt;fiend&lt;/td&gt;&lt;td&gt;find&lt;/td&gt;&lt;td&gt;fined&lt;/td&gt;&lt;td&gt;foined&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;fond&lt;/td&gt;&lt;td&gt;fondu&lt;/td&gt;&lt;td&gt;fondue&lt;/td&gt;&lt;td&gt;found&lt;/td&gt;&lt;td&gt;fugued&lt;/td&gt;
		&lt;/tr&gt;
		&lt;tr&gt;
			&lt;td&gt;fund&lt;/td&gt;&lt;td&gt;fundi&lt;/td&gt;&lt;td&gt;queened&lt;/td&gt;&lt;td&gt;quinoid&lt;/td&gt;&lt;td&gt;quoined&lt;/td&gt;
		&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Using our encoding tool, we found 74,151 possibilities from &lt;code&gt;fend&lt;/code&gt; to &lt;code&gt;quey yuga yowie&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="decoding-and-encoding-tools"&gt;Decoding and Encoding Tools&lt;/h2&gt;

&lt;form&gt;
	&lt;fieldset&gt;
		&lt;legend&gt;Decode a string to digits&lt;/legend&gt;
		&lt;label for="ciphertext" style="display: inline-block; width: 15ch;"&gt;Ciphertext: &lt;/label&gt;
		&lt;input id="ciphertext" size="50" type="text" value="rk you a tune" /&gt;
		&lt;br /&gt;
		&lt;button id="decode-btn"&gt;Decode&lt;/button&gt;
		&lt;br /&gt;
		&lt;label for="plaintext" style="display: inline-block; width: 15ch;"&gt;Plaintext (digits): &lt;/label&gt;
		&lt;output for="ciphertext" id="plaintext" name="plaintext"&gt;&lt;/output&gt;
	&lt;/fieldset&gt;
&lt;/form&gt;

&lt;br /&gt;
&lt;form&gt;
	&lt;fieldset&gt;
		&lt;legend&gt;Encode digits to potential memorable strings&lt;/legend&gt;
		&lt;label for="plaintext-encode" style="display: inline-block; width: 15ch;"&gt;Plaintext (digits): &lt;/label&gt;
		&lt;input id="plaintext-encode" pattern="[0-9]+" size="5" type="text" value="123" /&gt;
		&lt;br /&gt;
		&lt;label for="plaintext-limit" style="display: inline-block; width: 15ch;"&gt;Limit (n values): &lt;/label&gt;
		&lt;input id="plaintext-limit" pattern="[0-9]+" size="5" type="text" value="200" /&gt;
		&lt;br /&gt;
		&lt;label for="plaintext-offset" style="display: inline-block; width: 15ch;"&gt;Offset (n values): &lt;/label&gt;
		&lt;input id="plaintext-offset" pattern="[0-9]+" size="5" type="text" value="0" /&gt;
		&lt;br /&gt;
		&lt;button id="encode-btn"&gt;Encode&lt;/button&gt;
		&lt;br /&gt;
		&lt;label for="ciphertext-encode"&gt;Ciphertext: &lt;/label&gt;
		&lt;output for="plaintext-encode" id="ciphertext-encode"&gt;&lt;ol id="ciphers"&gt;&lt;/ol&gt;&lt;/output&gt;
		&lt;br /&gt;
	&lt;/fieldset&gt;
&lt;/form&gt;

&lt;blockquote&gt;
&lt;p&gt;Note 1: These computations run on your computer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Note 2: The JavaScript code for &lt;code&gt;Encode&lt;/code&gt; uses &lt;a href="https://caniuse.com/mdn-javascript_statements_import_import_attributes"&gt;import attributes&lt;/a&gt; which, as of December 2024, are not supported on Firefox, but are expected to be available soon.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Note 3: Negative limits are treated as no limits (i.e. Infinity). Negative offsets are treated as no offset (i.e. zero).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="implementation"&gt;Implementation&lt;/h2&gt;
&lt;h3 id="mapping-consonants-to-digits"&gt;Mapping Consonants to Digits&lt;/h3&gt;
&lt;p&gt;Our implementation of the mapping is a fairly straight-forward switch case with fall-through:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div&gt;
&lt;table style="border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block;"&gt;&lt;tr&gt;&lt;td style="vertical-align: top; padding: 0; margin: 0; border: 0;"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 1
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 2
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 3
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 4
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 5
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 6
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 7
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 8
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 9
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;10
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;11
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;12
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;13
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;14
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;15
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;16
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;17
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;18
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;19
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;20
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;21
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;22
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;23
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;24
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;25
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;26
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;27
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;28
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;29
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;30
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;31
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;32
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;33
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-typescript"&gt;&lt;span style="color: #75715e;"&gt;/**
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * For a given letter, return the cipher digit based on Carroll's Memoria Technica scheme, or null if there
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * is no associated digit. This function will return null for vowels (including y), punctuation marks, and
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * any other non-consonants. Only single letter English consonants have values; case is ignored.
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; *
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * @param chr a single character
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; */&lt;/span&gt;
&lt;span style="color: #66d9ef;"&gt;export&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;function&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;consonant_value&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;chr&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;string&lt;/span&gt;)&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;number&lt;/span&gt; &lt;span style="color: #f92672;"&gt;|&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;null&lt;/span&gt; {
    &lt;span style="color: #66d9ef;"&gt;const&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;code16&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;number&lt;/span&gt; &lt;span style="color: #f92672;"&gt;|&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;undefined&lt;/span&gt; &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;chr&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;toLowerCase&lt;/span&gt;().&lt;span style="color: #a6e22e;"&gt;codePointAt&lt;/span&gt;(&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;);
    &lt;span style="color: #66d9ef;"&gt;switch&lt;/span&gt; (&lt;span style="color: #a6e22e;"&gt;code16&lt;/span&gt;) {
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;98&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'b'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;99&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'c'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;100&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'d'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;119&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'w'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;116&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'t'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;106&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'j'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;102&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'f'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;113&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'q'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;108&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'l'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;118&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'v'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt;;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;115&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'s'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;120&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'x'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt;;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;112&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'p'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;109&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'m'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt;;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;104&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'h'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;107&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'k'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt;;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;110&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'n'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;103&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'g'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;9&lt;/span&gt;;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;122&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'z'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt;
        &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;114&lt;/span&gt; &lt;span style="color: #75715e;"&gt;/*'r'*/&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;;
        &lt;span style="color: #66d9ef;"&gt;default&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;null&lt;/span&gt;;
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The original version (as suggested by the commented code) compared strings against  strings (JavaScript lacks a character type). Profiling revealed that more than half the time was being spent in StringEqual calls. By switching the comparisons from string equality to integer equality, the new code takes approximately 15% of the time than the older version or a speed-up greater than 6x.&lt;/p&gt;
&lt;h2 id="decoding-ciphertext"&gt;Decoding Ciphertext&lt;/h2&gt;
&lt;p&gt;The decoding process is essentially a functional &lt;code&gt;collect&lt;/code&gt;: map across all elements and keep those that are non-null.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div&gt;
&lt;table style="border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block;"&gt;&lt;tr&gt;&lt;td style="vertical-align: top; padding: 0; margin: 0; border: 0;"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 1
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 2
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 3
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 4
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 5
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 6
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 7
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 8
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 9
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;10
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;11
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;12
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;13
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;14
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;15
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;16
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;17
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-typescript"&gt;&lt;span style="color: #75715e;"&gt;/**
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * Return an array of numbers based on decoding s. If there are no (valid) consonants within s, returns
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * an empty array.
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; *
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * @param s word or phrase
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; */&lt;/span&gt;
&lt;span style="color: #66d9ef;"&gt;export&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;function&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;decode&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;s&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;string&lt;/span&gt;)&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;number&lt;/span&gt;[] {
    &lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;result&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;number&lt;/span&gt;[] &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; [];

    &lt;span style="color: #66d9ef;"&gt;for&lt;/span&gt;(&lt;span style="color: #66d9ef;"&gt;const&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;chr&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;of&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;s&lt;/span&gt;) {
        &lt;span style="color: #66d9ef;"&gt;const&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;v&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;number&lt;/span&gt; &lt;span style="color: #f92672;"&gt;|&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;null&lt;/span&gt; &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;consonant_value&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;chr&lt;/span&gt;);
        &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;v&lt;/span&gt; &lt;span style="color: #f92672;"&gt;!==&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;null&lt;/span&gt;) {
            &lt;span style="color: #a6e22e;"&gt;result&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;push&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;v&lt;/span&gt;);
        }
    }

    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;result&lt;/span&gt;;
}
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Alternatively, &lt;code&gt;TextEncoder&lt;/code&gt; could be used to build a &lt;code&gt;Uint8Array&lt;/code&gt; of byte values and &lt;code&gt;consonant_value&lt;/code&gt; could be given a byte value as an argument. However, since a character in UTF-8 may span multiple bytes, we would still need to capture the complexity of Unicode encoding somewhere.&lt;/p&gt;
&lt;h2 id="encoding-a-specific-plaintext-value"&gt;Encoding a (Specific) Plaintext Value&lt;/h2&gt;
&lt;p&gt;Using (Moby 2002), we previously developed a dictionary by mapping each word into a cipher value and then grouping words by a shared cipher value. Thus, generating a list of words that match a given plaintext digit string is a look-up:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div&gt;
&lt;table style="border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block;"&gt;&lt;tr&gt;&lt;td style="vertical-align: top; padding: 0; margin: 0; border: 0;"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 1
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 2
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 3
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 4
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 5
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 6
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 7
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 8
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 9
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;10
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;11
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;12
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-typescript"&gt;&lt;span style="color: #75715e;"&gt;/**
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * Return from the dictionary all words that have the plaintext value of n.
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; *
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * @param n plaintext value
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * @param dictionary plaintext value to array of words
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; */&lt;/span&gt;
&lt;span style="color: #66d9ef;"&gt;export&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;function&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;encode&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;n&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;string&lt;/span&gt;, &lt;span style="color: #a6e22e;"&gt;dictionary&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;Map&lt;/span&gt;&amp;lt;&lt;span style="color: #f92672;"&gt;string&lt;/span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;,&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;string&lt;/span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;[]&lt;/span&gt;&amp;gt;)&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;string&lt;/span&gt;[] {
    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;dictionary&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;has&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;n&lt;/span&gt;)) {
        &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;dictionary&lt;/span&gt;.&lt;span style="color: #66d9ef;"&gt;get&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;n&lt;/span&gt;)&lt;span style="color: #f92672;"&gt;!&lt;/span&gt;;
    } &lt;span style="color: #66d9ef;"&gt;else&lt;/span&gt; {
        &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; [];
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Using a JavaScript &lt;code&gt;Map&lt;/code&gt; for the dictionary rather than a JavaScript &lt;code&gt;Object&lt;/code&gt; is a bit of a personal quirk. JavaScript&amp;rsquo;s &lt;code&gt;Map&lt;/code&gt;s are restricted to string keys, which in this case is not a restriction for our domain. We appreciate &lt;code&gt;Map&lt;/code&gt;&amp;rsquo;s clear separation between keys and properties and likely improved performance given its less flexibility compared to &lt;code&gt;Object&lt;/code&gt;s.&lt;/p&gt;
&lt;p&gt;The exclamation point after the &lt;code&gt;get(n)&lt;/code&gt; call is a &lt;a href="https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#non-null-assertion-operator-postfix-"&gt;TypeScript type assertion&lt;/a&gt; informing the compiler that the return value is neither null nor undefined. The compiler is (not yet) smart enough to recognize that since &lt;code&gt;has&lt;/code&gt; returned true, &lt;code&gt;get&lt;/code&gt; cannot return undefined. At least, unless the dictionary is modified by another process.&lt;/p&gt;
&lt;h2 id="encoding-a-general-plaintext-value"&gt;Encoding a (General) Plaintext Value&lt;/h2&gt;
&lt;p&gt;While &lt;code&gt;encode&lt;/code&gt; returns single word ciphertexts that fully match a given plaintext, we will often need groups of partial matches for a given plaintext. &lt;code&gt;encode_all&lt;/code&gt; is a generator function that will yield one ciphertext at a time, where each ciphertext contains one or more words. As described in the &amp;lsquo;English Language Coverage/Branching Factor&amp;rsquo; section below, most plaintexts will yield large numbers of potential ciphertexts (e.g. thousands for three digits). Since the consumer of &lt;code&gt;encode_all&lt;/code&gt; is unlikely to need all the outputs at the same time, we use a generator or iterable design to avoid loading all the outputs into memory at once.&lt;/p&gt;
&lt;p&gt;(If &lt;code&gt;encode_all&lt;/code&gt; is a generator, why not &lt;code&gt;encode&lt;/code&gt;? In &lt;code&gt;encode&lt;/code&gt;&amp;rsquo;s case, the dictionary provides all outputs at once so we do not save any resources by yielding only a single entry from that dictionary at a time.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div&gt;
&lt;table style="border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block;"&gt;&lt;tr&gt;&lt;td style="vertical-align: top; padding: 0; margin: 0; border: 0;"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 1
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 2
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 3
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 4
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 5
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 6
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 7
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 8
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 9
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;10
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;11
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;12
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;13
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;14
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;15
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;16
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;17
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;18
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;19
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;20
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;21
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;22
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;23
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;24
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;25
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;26
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;27
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;28
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;29
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;30
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;31
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;32
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;33
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-typescript"&gt;&lt;span style="color: #75715e;"&gt;/**
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * Return all phrases (space delimited words from the dictionary) that encode the sequence of
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * digits in n.
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; *
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * @param n sequence of digits (plaintext)
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; * @param dictionary plaintext value to array of words
&lt;/span&gt;&lt;span style="color: #75715e;"&gt; */&lt;/span&gt;
&lt;span style="color: #66d9ef;"&gt;export&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;function&lt;/span&gt;&lt;span style="color: #f92672;"&gt;*&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;encode_all&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;n&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;string&lt;/span&gt;, &lt;span style="color: #a6e22e;"&gt;dictionary&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;Map&lt;/span&gt;&amp;lt;&lt;span style="color: #f92672;"&gt;string&lt;/span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;,&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;string&lt;/span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;[]&lt;/span&gt;&amp;gt;)&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;Generator&lt;/span&gt;&amp;lt;&lt;span style="color: #f92672;"&gt;string&lt;/span&gt;&amp;gt; {
    &lt;span style="color: #66d9ef;"&gt;const&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;digits&lt;/span&gt; &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;n&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;split&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;''&lt;/span&gt;);
    &lt;span style="color: #66d9ef;"&gt;const&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;nonEmpty&lt;/span&gt; &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; (&lt;span style="color: #a6e22e;"&gt;seq&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;any&lt;/span&gt;[]) &lt;span style="color: #f92672;"&gt;=&amp;gt;&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;seq&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;length&lt;/span&gt; &lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;;

    &lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;divisors&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;number&lt;/span&gt;[] &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; [];
    &lt;span style="color: #66d9ef;"&gt;for&lt;/span&gt;(&lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;i&lt;/span&gt; &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;; &lt;span style="color: #a6e22e;"&gt;i&lt;/span&gt; &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;digits&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;length&lt;/span&gt;; &lt;span style="color: #a6e22e;"&gt;i&lt;/span&gt;&lt;span style="color: #f92672;"&gt;++&lt;/span&gt;) {
        &lt;span style="color: #a6e22e;"&gt;divisors&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;push&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;i&lt;/span&gt;);
    }

    &lt;span style="color: #66d9ef;"&gt;for&lt;/span&gt;(&lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;divisor&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;of&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;subsets&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;divisors&lt;/span&gt;)) {
        &lt;span style="color: #a6e22e;"&gt;divisor&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;unshift&lt;/span&gt;(&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;);  &lt;span style="color: #75715e;"&gt;// include all digits by starting the slice at index 0
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;&lt;/span&gt;        &lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;slices&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;string&lt;/span&gt;[][] &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;sliceIndices&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;digits&lt;/span&gt;, &lt;span style="color: #a6e22e;"&gt;divisor&lt;/span&gt;);

        &lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;phrases&lt;/span&gt;: &lt;span style="color: #66d9ef;"&gt;string&lt;/span&gt;[][] &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; [];
        &lt;span style="color: #66d9ef;"&gt;for&lt;/span&gt;(&lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;slice&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;of&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;slices&lt;/span&gt;) {
            &lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;words&lt;/span&gt; &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;encode&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;slice&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;join&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;''&lt;/span&gt;), &lt;span style="color: #a6e22e;"&gt;dictionary&lt;/span&gt;)
            &lt;span style="color: #a6e22e;"&gt;phrases&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;push&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;words&lt;/span&gt;);
        }

        &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;phrases&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;every&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;nonEmpty&lt;/span&gt;)) {
            &lt;span style="color: #66d9ef;"&gt;for&lt;/span&gt;(&lt;span style="color: #66d9ef;"&gt;let&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;wrappedPhrase&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;of&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;cartesian&lt;/span&gt;(&lt;span style="color: #a6e22e;"&gt;phrases&lt;/span&gt;)) {
                &lt;span style="color: #66d9ef;"&gt;yield&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;wrappedPhrase&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;underlying&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;join&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;' '&lt;/span&gt;);
            }
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Line 9 separates a string of digits into an array of digit characters. This function assumes digits will only contain the characters &amp;lsquo;0&amp;rsquo; to &amp;lsquo;9&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;This function needs to check all ordered sub-sequences of the sequence, &lt;code&gt;digits&lt;/code&gt;. For example, if digits is &lt;code&gt;['1', '2', '3']&lt;/code&gt;, then we will attempt to &lt;code&gt;encode&lt;/code&gt;: &lt;code&gt;['1', '2', '3'], ['1'] + ['2', '3], ['1', '2'] + ['3'],&lt;/code&gt; and &lt;code&gt;['1'] + ['2'] + ['3']&lt;/code&gt;. To do this, we incrementally add zero or more divisors that separate the array. A divisor splits an array from a given index (inclusive) to the right. Each split is referred to as a &amp;lsquo;slice&amp;rsquo;.&lt;/p&gt;
&lt;p&gt;In lines 21 through 25, we &lt;code&gt;encode&lt;/code&gt; each slice (the &lt;code&gt;slice.join('')&lt;/code&gt; transforms an array of digits into a string of digits) and collect all possible words under an array of arrays termed &lt;code&gt;phrases&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If all positions within a phrase contain at least one word (lines 27 through 31), then we perform a cartesian join between each index. A cartesian join or product is similar to a nested for-loop and generates all possible tuples. The &lt;code&gt;yield&lt;/code&gt; gives the caller a single value (a space-separated list of words) while retaining state so the next value can be retrieved without re-computing earlier elements.&lt;/p&gt;
&lt;p&gt;The code for &lt;code&gt;subsets&lt;/code&gt;, &lt;code&gt;sliceIndices&lt;/code&gt;, and &lt;code&gt;cartesian&lt;/code&gt; (among other functions) is in &lt;a href="https://gitlab.com/jeffrey_starr/memoria_technica/-/blob/master/util.ts?ref_type=heads"&gt;&lt;code&gt;util.ts&lt;/code&gt;&lt;/a&gt; in this project&amp;rsquo;s gitlab repository. Knuth&amp;rsquo;s &lt;em&gt;The Art of Programming, Volume 4A&lt;/em&gt; is a useful resource for these kinds of algorithms.&lt;/p&gt;
&lt;h2 id="suitability-for-steganography"&gt;Suitability for Steganography&lt;/h2&gt;
&lt;p&gt;Steganography is the art of hiding information in plain sight. While cryptography obscures the message so the attackers have difficulty reading it, attackers will know  that the message is not meant to be read. In contrast, steganography &lt;em&gt;may&lt;/em&gt; trick the attackers into believing the message is benign or not noticing the message at all. For example, invisible ink and microdots are forms of steganography. Similarly, a message may be hidden by distorting specific pixels of an image. Steganography and cryptography may be combined with the message first encrypted and then hidden.&lt;/p&gt;
&lt;p&gt;While Dodgson did not present this technique as a cipher nor as a tool for steganography, but only as an aid to memory, we believe it has potential utility.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s try to hide phi, 1.61803, as a classified ad. The ad will start with &amp;lsquo;For sale: ' and the item for sale will be an encoding of the number. We will assume the reader can place the decimal point appropriately (or we transmit that data via some other way). Using the encode tool above, &lt;code&gt;161803&lt;/code&gt; yields some promising outputs within the first few hundred cases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&amp;ldquo;box chart&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;boys chariot&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&amp;ldquo;cosy chariot&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Would an attacker suspect ads like these?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For sale: Box Chart 2016. Software library for R. $1 + postage.&lt;/li&gt;
&lt;li&gt;For sale: Boy&amp;rsquo;s Chariot. Clean, blue, ages 3-6. $100 OBO.&lt;/li&gt;
&lt;li&gt;For sale: Cosy Chariot. Padded seats, yellow. One seater.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We chose these three examples after scanning about 3,000 outputs. The dictionary used by the tool contains almost 118,000 words (including plural and forms of tenses).&lt;/p&gt;
&lt;h3 id="english-language-coveragebranching-factor"&gt;English Language Coverage/Branching Factor&lt;/h3&gt;
&lt;p&gt;The branching factor (number of words in the dictionary that match a given sequence) is numerous for single and double digit sequences. (Note that leading zeros are allowed.) The first table below represents the number of words that perfectly match the single digit sequence while the second table represents two digit sequences.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Digit&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Count&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;0&lt;/td&gt;
&lt;td style="text-align: right;"&gt;63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1&lt;/td&gt;
&lt;td style="text-align: right;"&gt;43&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;2&lt;/td&gt;
&lt;td style="text-align: right;"&gt;53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;3&lt;/td&gt;
&lt;td style="text-align: right;"&gt;45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;4&lt;/td&gt;
&lt;td style="text-align: right;"&gt;21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;5&lt;/td&gt;
&lt;td style="text-align: right;"&gt;54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;46&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;7&lt;/td&gt;
&lt;td style="text-align: right;"&gt;57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;8&lt;/td&gt;
&lt;td style="text-align: right;"&gt;51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;9&lt;/td&gt;
&lt;td style="text-align: right;"&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;&lt;/th&gt;
&lt;th style="text-align: right;"&gt;0&lt;/th&gt;
&lt;th style="text-align: right;"&gt;1&lt;/th&gt;
&lt;th style="text-align: right;"&gt;2&lt;/th&gt;
&lt;th style="text-align: right;"&gt;3&lt;/th&gt;
&lt;th style="text-align: right;"&gt;4&lt;/th&gt;
&lt;th style="text-align: right;"&gt;5&lt;/th&gt;
&lt;th style="text-align: right;"&gt;6&lt;/th&gt;
&lt;th style="text-align: right;"&gt;7&lt;/th&gt;
&lt;th style="text-align: right;"&gt;8&lt;/th&gt;
&lt;th style="text-align: right;"&gt;9&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;0x&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;31&lt;/td&gt;
&lt;td style="text-align: right;"&gt;36&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59&lt;/td&gt;
&lt;td style="text-align: right;"&gt;44&lt;/td&gt;
&lt;td style="text-align: right;"&gt;12&lt;/td&gt;
&lt;td style="text-align: right;"&gt;61&lt;/td&gt;
&lt;td style="text-align: right;"&gt;82&lt;/td&gt;
&lt;td style="text-align: right;"&gt;43&lt;/td&gt;
&lt;td style="text-align: right;"&gt;23&lt;/td&gt;
&lt;td style="text-align: right;"&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;1x&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;65&lt;/td&gt;
&lt;td style="text-align: right;"&gt;41&lt;/td&gt;
&lt;td style="text-align: right;"&gt;53&lt;/td&gt;
&lt;td style="text-align: right;"&gt;61&lt;/td&gt;
&lt;td style="text-align: right;"&gt;9&lt;/td&gt;
&lt;td style="text-align: right;"&gt;69&lt;/td&gt;
&lt;td style="text-align: right;"&gt;71&lt;/td&gt;
&lt;td style="text-align: right;"&gt;43&lt;/td&gt;
&lt;td style="text-align: right;"&gt;46&lt;/td&gt;
&lt;td style="text-align: right;"&gt;61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;2x&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;61&lt;/td&gt;
&lt;td style="text-align: right;"&gt;31&lt;/td&gt;
&lt;td style="text-align: right;"&gt;54&lt;/td&gt;
&lt;td style="text-align: right;"&gt;37&lt;/td&gt;
&lt;td style="text-align: right;"&gt;9&lt;/td&gt;
&lt;td style="text-align: right;"&gt;70&lt;/td&gt;
&lt;td style="text-align: right;"&gt;65&lt;/td&gt;
&lt;td style="text-align: right;"&gt;43&lt;/td&gt;
&lt;td style="text-align: right;"&gt;27&lt;/td&gt;
&lt;td style="text-align: right;"&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;3x&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;44&lt;/td&gt;
&lt;td style="text-align: right;"&gt;25&lt;/td&gt;
&lt;td style="text-align: right;"&gt;37&lt;/td&gt;
&lt;td style="text-align: right;"&gt;29&lt;/td&gt;
&lt;td style="text-align: right;"&gt;6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;38&lt;/td&gt;
&lt;td style="text-align: right;"&gt;48&lt;/td&gt;
&lt;td style="text-align: right;"&gt;45&lt;/td&gt;
&lt;td style="text-align: right;"&gt;30&lt;/td&gt;
&lt;td style="text-align: right;"&gt;52&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;4x&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;53&lt;/td&gt;
&lt;td style="text-align: right;"&gt;10&lt;/td&gt;
&lt;td style="text-align: right;"&gt;21&lt;/td&gt;
&lt;td style="text-align: right;"&gt;27&lt;/td&gt;
&lt;td style="text-align: right;"&gt;8&lt;/td&gt;
&lt;td style="text-align: right;"&gt;35&lt;/td&gt;
&lt;td style="text-align: right;"&gt;26&lt;/td&gt;
&lt;td style="text-align: right;"&gt;11&lt;/td&gt;
&lt;td style="text-align: right;"&gt;7&lt;/td&gt;
&lt;td style="text-align: right;"&gt;33&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;5x&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;50&lt;/td&gt;
&lt;td style="text-align: right;"&gt;34&lt;/td&gt;
&lt;td style="text-align: right;"&gt;61&lt;/td&gt;
&lt;td style="text-align: right;"&gt;41&lt;/td&gt;
&lt;td style="text-align: right;"&gt;12&lt;/td&gt;
&lt;td style="text-align: right;"&gt;65&lt;/td&gt;
&lt;td style="text-align: right;"&gt;75&lt;/td&gt;
&lt;td style="text-align: right;"&gt;44&lt;/td&gt;
&lt;td style="text-align: right;"&gt;24&lt;/td&gt;
&lt;td style="text-align: right;"&gt;95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;6x&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;37&lt;/td&gt;
&lt;td style="text-align: right;"&gt;20&lt;/td&gt;
&lt;td style="text-align: right;"&gt;27&lt;/td&gt;
&lt;td style="text-align: right;"&gt;37&lt;/td&gt;
&lt;td style="text-align: right;"&gt;6&lt;/td&gt;
&lt;td style="text-align: right;"&gt;46&lt;/td&gt;
&lt;td style="text-align: right;"&gt;42&lt;/td&gt;
&lt;td style="text-align: right;"&gt;40&lt;/td&gt;
&lt;td style="text-align: right;"&gt;29&lt;/td&gt;
&lt;td style="text-align: right;"&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;7x&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;78&lt;/td&gt;
&lt;td style="text-align: right;"&gt;41&lt;/td&gt;
&lt;td style="text-align: right;"&gt;64&lt;/td&gt;
&lt;td style="text-align: right;"&gt;49&lt;/td&gt;
&lt;td style="text-align: right;"&gt;9&lt;/td&gt;
&lt;td style="text-align: right;"&gt;71&lt;/td&gt;
&lt;td style="text-align: right;"&gt;94&lt;/td&gt;
&lt;td style="text-align: right;"&gt;67&lt;/td&gt;
&lt;td style="text-align: right;"&gt;36&lt;/td&gt;
&lt;td style="text-align: right;"&gt;107&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;8x&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;34&lt;/td&gt;
&lt;td style="text-align: right;"&gt;13&lt;/td&gt;
&lt;td style="text-align: right;"&gt;41&lt;/td&gt;
&lt;td style="text-align: right;"&gt;23&lt;/td&gt;
&lt;td style="text-align: right;"&gt;8&lt;/td&gt;
&lt;td style="text-align: right;"&gt;41&lt;/td&gt;
&lt;td style="text-align: right;"&gt;50&lt;/td&gt;
&lt;td style="text-align: right;"&gt;38&lt;/td&gt;
&lt;td style="text-align: right;"&gt;28&lt;/td&gt;
&lt;td style="text-align: right;"&gt;37&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;9x&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;56&lt;/td&gt;
&lt;td style="text-align: right;"&gt;35&lt;/td&gt;
&lt;td style="text-align: right;"&gt;55&lt;/td&gt;
&lt;td style="text-align: right;"&gt;48&lt;/td&gt;
&lt;td style="text-align: right;"&gt;8&lt;/td&gt;
&lt;td style="text-align: right;"&gt;68&lt;/td&gt;
&lt;td style="text-align: right;"&gt;83&lt;/td&gt;
&lt;td style="text-align: right;"&gt;56&lt;/td&gt;
&lt;td style="text-align: right;"&gt;20&lt;/td&gt;
&lt;td style="text-align: right;"&gt;84&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There are 78 three-digit sequences where do we not see any single word in the dictionary (although these three-digits could be separated into one and two-digit subsequences): 008, 014, 048, 084, 088, 128, 147, 148, 204, 224, 234, 248, 264, 294, 314, 324, 334, 342, 347, 348, 349, 354, 364, 394, 404, 411, 414, 421, 423, 424, 427, 428, 434, 437, 447, 464, 467, 471, 474, 481, 483, 484, 487, 494, 534, 541, 547, 564, 574, 581, 584, 624, 634, 644, 661, 747, 748, 774, 784, 814, 817, 821, 827, 834, 837, 841, 845, 847, 848, 849, 864, 874, 881, 914, 948, 974, 981, 984.&lt;/p&gt;
&lt;p&gt;If the specific words do not matter, then any given plaintext can be encoded into a reasonable selection of words. If specific words do matter (i.e. the ciphertext is being hidden), then this branching factor may not be sufficient to cover all contexts. For instance, it may not be possible to consistently hide messages within recipes since the number of ingredient-related words is restrictive.&lt;/p&gt;
&lt;h3 id="weakness-benfords-law-versus-letter-frequencies"&gt;Weakness? Benford&amp;rsquo;s Law versus Letter Frequencies&lt;/h3&gt;
&lt;p&gt;A potential weakness of this approach as a steganographic technique is that the frequency of digits does not match the frequency of consonants. For instance, the digit 4 maps to &amp;lsquo;f&amp;rsquo; and &amp;lsquo;q&amp;rsquo;. In studies of Benford&amp;rsquo;s Law, which measures the frequency of digits in real-world contexts, a 4 is predicted as the leading digit in 9.7% of all numbers (Berger 2015). However, &amp;lsquo;f&amp;rsquo; is seen as the leading letter &lt;a href="https://www3.nd.edu/~busiforc/handouts/cryptography/letterfrequencies.html"&gt;in 2% of words and &amp;lsquo;q&amp;rsquo; is very rare&lt;/a&gt;. Thus, a study of letter frequencies in a passage could detect an anomaly based on the imbalance. However, unless the volume of messages is high or the plaintext is very long, we suspect this would fall into the noise.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;p&gt;(Abeles 2005) Abeles, Francine F. 2005. &lt;em&gt;Lewis Carroll&amp;rsquo;s ciphers: The literary connections.&lt;/em&gt; Advances in Applied Mathematics,
Volume 34, Issue 4. Pages 697-708. ISSN 0196-8858, &lt;a href="https://doi.org/10.1016/j.aam.2004.06.006"&gt;https://doi.org/10.1016/j.aam.2004.06.006&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Berger 2015) Berger, Arno, and Theodore Preston Hill. 2015. &lt;em&gt;An Introduction to Benford’s Law.&lt;/em&gt; Princeton, New Jersey: Princeton University Press. &lt;a href="https://assets.press.princeton.edu/chapters/s10526.pdf"&gt;First Chapter&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(Gardner 1996) Gardner, Martin, and Lewis Carroll. 1996. &lt;em&gt;The Universe in a Handkerchief : Lewis Carroll’s Mathematical Recreations,
Games, Puzzles, and Word Plays.&lt;/em&gt; New York: Copernicus. Pages 31-36.&lt;/p&gt;
&lt;p&gt;(Moby 2002) Ward, Grady. 2002. &lt;em&gt;Moby Word II&lt;/em&gt;. &lt;a href="https://www.gutenberg.org/ebooks/3201"&gt;https://www.gutenberg.org/ebooks/3201&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://gitlab.com/jeffrey_starr/memoria_technica"&gt;Gitlab Repository&lt;/a&gt;. &lt;a href="https://gitlab.com/jeffrey_starr/memoria_technica"&gt;https://gitlab.com/jeffrey_starr/memoria_technica&lt;/a&gt;&lt;/p&gt;</description><author>ℤ→ℤ</author><pubDate>Thu, 26 Dec 2024 22:44:43 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/memoria_technica/</guid></item><item><title>The complete text of "All Watched Over by Machines of Loving Grace"</title><link>http://blog.jgc.org/2024/12/the-complete-text-of-all-watched-over.html</link><description>&lt;p&gt;Richard Brautigan's poem "&lt;a href="https://en.wikipedia.org/wiki/All_Watched_Over_by_Machines_of_Loving_Grace"&gt;All Watched Over by Machines of Loving Grace&lt;/a&gt;" is somewhat well known in tech. circles but I couldn't find a complete PDF of the &lt;a href="https://en.wikipedia.org/wiki/All_Watched_Over_by_Machines_of_Loving_Grace_(poetry_collection)"&gt;original 1967 publication of it (and other poems)&lt;/a&gt; online.&amp;nbsp;&lt;/p&gt;&lt;p&gt;The copyright notice in Brautigan's collection reads:&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;&amp;nbsp;&amp;nbsp;© Copyright 1967 by Richard Brautigan&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: courier;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;Permission is granted to reprint&lt;br /&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;any of these poems in magazines,&lt;br /&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;books and newspapers if they are&lt;br /&gt;&lt;span&gt;&amp;nbsp; &lt;/span&gt;given away free.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;I am interpreting "print" as including the availability of a free PDF and making a scan of the complete book available here.&amp;nbsp;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://jgc.org/awobmolg-1967.pdf" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="640" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjGhScUr9y4lWyAZs6B-j7QtJjfqFmt-VrOpfII9BIJYzoGDGCtdUYR0pffEwSIWwDUrIBaj57cz5Uluj8CG1hzA8Oi6Vyvm9Bc3wcDkAxucLJQJGrQHOOYLfwIE0zc-JsnuKfAkwrxQPe1N1RdLV7uOhfQD84r6oCgMuq39ZxVXOTT3sNgxGv9CA/w532-h640/Screenshot%202024-12-26%20at%2015.24.00.png" width="532" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;&lt;br /&gt;&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Thu, 26 Dec 2024 17:34:02 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2024/12/the-complete-text-of-all-watched-over.html</guid></item><item><title>A Reply to Don Geddis</title><link>https://blog.rongarret.info/2024/12/a-reply-to-don-geddis.html</link><description>Don Geddis left a comment on my last post.&amp;nbsp; My reply grew far longer than would reasonably fit into a comment reply so I decided to post it as an article.&amp;nbsp; Don wrote:I wonder if you've considered that perhaps you have more in common with the people who frustrate you, than your current self-image suggests.My reply:I've not just considered it, I will happily concede that I am not as</description><author>Rondam Ramblings</author><pubDate>Thu, 26 Dec 2024 10:12:48 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2024/12/a-reply-to-don-geddis.html</guid></item><item><title>Books I read in 2024</title><link>https://nicolaiarocci.com/books-i-read-in-2024/</link><description>&lt;p&gt;I read 30 books or 8365 pages in 2024, a solid improvement over &lt;a href="https://nicolaiarocci.com/books-i-read-in-2023/"&gt;last
year&lt;/a&gt;&amp;rsquo;s, and many of those books were excellent.
&lt;em&gt;Lolita&lt;/em&gt; by Vladimir Nabokov was outstanding, Marcus Aurelius&amp;rsquo; &lt;em&gt;Meditations&lt;/em&gt; was
incredible, and then there&amp;rsquo;s &lt;em&gt;Family Lexicon&lt;/em&gt; and &lt;em&gt;The garden of Finzi-Contini&lt;/em&gt;,
and many others were close to that league. Yeah, color me satisfied.&lt;/p&gt;
&lt;p&gt;The usual scoring system applies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;One star means a book is meh.&lt;/li&gt;
&lt;li&gt;Two stars mean a book is perfectly fine.&lt;/li&gt;
&lt;li&gt;Three stars mean a book is good—consider it recommended.&lt;/li&gt;
&lt;li&gt;Four stars mean a book is exceptional.&lt;/li&gt;
&lt;li&gt;Five stars is pretty much unheard of.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="the-question-of-palestine-by-ew-said"&gt;The question of Palestine, by E.W. Said&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;(La Questione Palestinese, il Saggiatore)&lt;/em&gt;&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Thu, 26 Dec 2024 08:36:24 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/books-i-read-in-2024/</guid></item><item><title>RSS Feeds and Real Time Crawling</title><link>https://www.marginalia.nu/log/a_113_rtc/</link><description>&lt;p&gt;A while back an update went live that, with some caveats, changes the time it takes for an update on a website to reflect in the search engine index from up to 2 months to 1-2 days. Conditions being if the website has an RSS or Atom feed.&lt;/p&gt;
&lt;p&gt;The big crawl job takes about two months, and is run partition by partition, meaning there&amp;rsquo;s typically a slice of the index that is two months stale at any given point in time. To help compensate for this, a new crawler and index partition has been added that focuses on recently updated content.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Thu, 26 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_113_rtc/</guid></item><item><title>Picking up volleyball in NYC with Goodrec and New York Urban</title><link>http://notes.eatonphil.com/2024-12-26-volleyball-in-nyc.html</link><description>&lt;p&gt;I was so intimidated to go at first, but it is in fact easy and fun to
start playing beginner volleyball in New York. The people are so
friendly and welcoming that it has been easy to keep playing
consistently every week since I started for the first time this
August. It's been a great workout and a great way to make friends!&lt;/p&gt;
&lt;p&gt;The two platforms I've used to find volleyball games are
&lt;a href="https://www.goodrec.com/"&gt;Goodrec&lt;/a&gt; and &lt;a href="https://www.nyurban.com/"&gt;New York
Urban&lt;/a&gt;. While these platforms may also offer
classes and leagues, I mostly use them to play "pickup" games. Pickup
games are where you show up and join (or get assigned to) a team to
play for an hour or two. Easy to go on your own or with friends.&lt;/p&gt;
&lt;p&gt;I'm not an expert! My only hope with this post is that maybe it makes
trying out volleyball in New York feel a little less intimidating for
you!&lt;/p&gt;
&lt;h3 id="goodrec"&gt;Goodrec&lt;/h3&gt;&lt;p&gt;With Goodrec you have to use their mobile app. Beginner tier is called
"social" on Goodrec. So browse available games until you find one at
the level you want to play. You enroll in (buy a place in) sessions
individually.&lt;/p&gt;
&lt;p&gt;Sessions are between 90-120 minutes long.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/goodrec-social.png" src="/assets/goodrec-social.png" /&gt;&lt;/p&gt;
&lt;p&gt;They ask you not to arrive more than 10 minutes early at the gym. When
you arrive you tell the gym managers (usually in a desk up front
somewhere) you're there for Goodrec and the tier (in case the gym has
multiple level games going on at the same time). Then you wait until
the Goodrec "host" arrives and they will organize everyone into
teams.&lt;/p&gt;
&lt;p&gt;Goodrec hosts are players who volunteer to organize the games. They'll
explain the rules of the game (makes Goodrec very good for beginners)
and otherwise help you out.&lt;/p&gt;
&lt;p&gt;Always say thank you to your host!&lt;/p&gt;
&lt;h3 id="new-york-urban"&gt;New York Urban&lt;/h3&gt;&lt;p&gt;With New York Urban, pickup sessions are called &lt;a href="https://www.nyurban.com/open-play-volleyball"&gt;"open
play"&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There is no mobile app, you just use the website to purchase a spot in
a session. The sessions are longer and cheaper than Goodrec. But there
is no host; players self-organize.&lt;/p&gt;
&lt;p&gt;The options are more limited too. You play at one of four high schools
on either a Friday night or on Sunday. And session slots tend to sell
out much more quickly than with Goodrec.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/nyurban-beginner.png" src="/assets/nyurban-beginner.png" /&gt;&lt;/p&gt;
&lt;h3 id="big-city-volleyball"&gt;Big City Volleyball&lt;/h3&gt;&lt;p&gt;You can also check out &lt;a href="https://bigcityvolleyball.com/"&gt;Big City
Volleyball&lt;/a&gt; but I haven't used it yet.&lt;/p&gt;
&lt;h3 id="volo"&gt;Volo&lt;/h3&gt;&lt;p&gt;I haven't ever done Volo but I think I've heard it described as "beer
league". That even some of the beginner tier sessions with Goodrec and
New York Urban are more competitive.&lt;/p&gt;
&lt;p&gt;But also, Volo is built around leagues so you have to get the timing
right. Goodrec's and New York Urban's pickup games make it easy to get
started playing any time of year.&lt;/p&gt;
&lt;h3 id="making-friends"&gt;Making friends&lt;/h3&gt;&lt;p&gt;It was super awkward to go at first! I went by myself. I didn't know
what I was doing. I couldn't remember, and didn't know, many rules. I
didn't have court shoes or knee pads.&lt;/p&gt;
&lt;p&gt;But the Goodrec host system is particularly great for bringing
beginners in and making them feel welcome. You have a great time even
if you're terrible.&lt;/p&gt;
&lt;p&gt;The first game I went to, I tried to hang out afterward to meet people.
But people either came with their SO or with their friends or by
themselves so they all just left immediately or hung out in their
group.&lt;/p&gt;
&lt;p&gt;So you can't just go once and expect to make friends immediately. But
if you keep going at the same place and time regularly week over week,
you'll see familiar faces. Maybe half the people I play with each
week are regulars. If you're friendly you'll start making friends with
these people and eventually start going out to bars with them after
the games.&lt;/p&gt;
&lt;h3 id="improving"&gt;Improving&lt;/h3&gt;&lt;p&gt;Even if you find yourself embarrassingly bad at first, just keep
going! I'm 29, 6'1, 190lbs and from observation the past 5 months,
age, height, and weight have a very indirect relation to playing
ability.&lt;/p&gt;
&lt;p&gt;Most of the people who play are self-taught, especially at the lower
tiers I've played at. But some people played for the school team in
high school or college. These people are fun to play with and you can
learn a lot from them.&lt;/p&gt;
&lt;p&gt;Most people who are self-taught seem to watch YouTube videos like
&lt;a href="https://www.youtube.com/channel/UCoEMagRUvrXELuJZwS4DevA"&gt;Coach
Donny&lt;/a&gt;,
helpful for learning how to serve, set, block, etc. Or they take
"clinics" (classes) with Goodrec or other platforms. (I have no idea
about these, I've never done them before.)&lt;/p&gt;
&lt;p&gt;At first I played 2 hours a week and I was completely exhausted after
the session. Over time it got easier so I started playing 2-3 sessions
a week (6-9-ish hours). With practice and consistency (after about 3-4
months), I started playing Intermediate tier with Goodrec and New York
Urban. And I don't think I'll play Beginner/Social at all anymore.&lt;/p&gt;
&lt;p&gt;I still primarily play for fun and for the workout and to meet
people. But it's also fun to get better!&lt;/p&gt;
&lt;p&gt;I played with one person much better than myself in an Intermediate
session one time and he mentioned he will probably stop playing
Intermediate and only play High Intermediate. He mentioned you get
better when you keep pushing yourself to play with better and better
players. Good advice!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a little post on picking up volleyball in new york.&lt;br /&gt;&lt;br /&gt;It's fun, and a great workout, and you meet interesting people!&lt;a href="https://t.co/jEWHbRWF6C"&gt;https://t.co/jEWHbRWF6C&lt;/a&gt; &lt;a href="https://t.co/ipuIUB1ZnM"&gt;pic.twitter.com/ipuIUB1ZnM&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1872394142212661250?ref_src=twsrc%5Etfw"&gt;December 26, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 26 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-12-26-volleyball-in-nyc.html</guid></item><item><title>Low and steady write rates can be misleading</title><link>https://sophiabits.com/blog/low-and-steady-write-rates</link><description>&lt;p&gt;A common mistake in system design interviews is to underestimate a low but steady write rate. Many candidates quickly dismiss a write rate of 1 request per second as insignificant, but this isn’t always the case—especially in a startup environment where growth is measured week-over-week.&lt;/p&gt;&lt;p&gt;Small write volumes can accumulate significantly over time and cause all sorts of downstream problems. During a major Postgres version upgrade, Retool found that half of their 4 TB database was taken up by audit events—many of which they didn’t &lt;em&gt;need&lt;/em&gt; to be readily accessible due to their age. At 1 write per second, you will accumulate 31.5m rows over the course of the year. This isn’t an overwhelming number of rows—depending on the details of what you’re doing, this is most likely fine—but it’s a larger number than most people expect.&lt;/p&gt;&lt;p&gt;That 31.5m figure assumes your company sees zero growth over the year, which is unlikely to be true. A successful startup will grow week-over-week, and weekly growth compounded over the course of a year can balloon writes significantly. The graph below shows the cumulative number of rows written during year 1 under three different weekly growth rates:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Read more on &lt;a href="https://sophiabits.com/blog/low-and-steady-write-rates"&gt;sophiabits.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description><author>Sophia Willows' Blog</author><pubDate>Thu, 26 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://sophiabits.com/blog/low-and-steady-write-rates</guid></item><item><title>I ❤️ triathlons</title><link>https://bytepawn.com/i-love-triathlons.html</link><description>&lt;p&gt;In 2008, I completed my first half-Ironman — then, later that same year, I took on the full Ironman distance at the traditional ExtremeMan in Hungary, finishing in 13 hours and 38 minutes. By 2013, I had knocked my time down to 11 hours and 36 minutes. Since then, I’ve racked up a total of 56 triathlon finishes. 2024 was one of the best years so far, with a record of 10 triathlon races.&lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/cervelo-p5.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Thu, 26 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/i-love-triathlons.html</guid></item><item><title>Happy Holidays: Snowflakes</title><link>https://www.xorvoid.com/happy_holidays.html</link><description>Happy Holidays: Snowflakes</description><author>xorvoid</author><pubDate>Wed, 25 Dec 2024 16:54:50 GMT</pubDate><guid isPermaLink="true">https://www.xorvoid.com/happy_holidays.html</guid></item><item><title>The Only Self-Improvement Article You Will Ever Need. Identity is Everything.</title><link>https://joshbaldwin.substack.com/p/the-only-self-improvement-article</link><description>Hey now, I signed up for moral philosophy and thoughts on the pro-animal movement.</description><author>Josh Baldwin</author><pubDate>Wed, 25 Dec 2024 15:08:35 GMT</pubDate><guid isPermaLink="true">https://joshbaldwin.substack.com/p/the-only-self-improvement-article</guid></item><item><title>Merry Christmas</title><link>https://www.xorvoid.com/merry_christmas.html</link><description>Merry Christmas</description><author>xorvoid</author><pubDate>Wed, 25 Dec 2024 10:14:33 GMT</pubDate><guid isPermaLink="true">https://www.xorvoid.com/merry_christmas.html</guid></item><item><title>Catching up with async Rust</title><link>https://fasterthanli.me/articles/catching-up-with-async-rust</link><description>&lt;p&gt;In December 2023, a minor miracle happened: &lt;a href="https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html"&gt;async fn in traits&lt;/a&gt; shipped.&lt;/p&gt;

&lt;p&gt;As of Rust 1.39, we already had free-standing async functions:&lt;/p&gt;

&lt;figure class="code-block has-language-tag"&gt;&lt;span class="language-tag" title="Rust"&gt;&lt;/span&gt;&lt;code class="scroll-wrapper"&gt;pub async fn read_hosts() -&amp;gt; eyre::Result&amp;lt;Vec&amp;lt;u8&amp;gt;&amp;gt; {
    // etc.
}
&lt;/code&gt;&lt;/figure&gt;&lt;p&gt;…and async functions in impl blocks:&lt;/p&gt;

&lt;figure class="code-block has-language-tag"&gt;&lt;span class="language-tag" title="Rust"&gt;&lt;/span&gt;&lt;code class="scroll-wrapper"&gt;impl HostReader {
    pub async fn read_hosts(&amp;amp;self) -&amp;gt; eyre::Result&amp;lt;Vec&amp;lt;u8&amp;gt;&amp;gt; {
        // etc.
    }
}
&lt;/code&gt;&lt;/figure&gt;&lt;p&gt;But we did not have async functions in traits:&lt;/p&gt;









&lt;a class="anchor" href="#the-size-of-locals" id="the-size-of-locals"&gt;&lt;/a&gt;


















&lt;!-- playwall --&gt;












&lt;a class="anchor" href="#just-boxing-it" id="just-boxing-it"&gt;&lt;/a&gt;


















&lt;a class="anchor" href="#dynamic-dispatch" id="dynamic-dispatch"&gt;&lt;/a&gt;
























&lt;a class="anchor" href="#dyn-compatibility" id="dyn-compatibility"&gt;&lt;/a&gt;




































&lt;a class="anchor" href="#associated-types" id="associated-types"&gt;&lt;/a&gt;















&lt;a class="anchor" href="#a-refreshed-service-trait" id="a-refreshed-service-trait"&gt;&lt;/a&gt;










&lt;a class="anchor" href="#unnameable-types" id="unnameable-types"&gt;&lt;/a&gt;










&lt;a class="anchor" href="#lifetimes-a-refresher" id="lifetimes-a-refresher"&gt;&lt;/a&gt;
















&lt;a class="anchor" href="#hidden-captures" id="hidden-captures"&gt;&lt;/a&gt;






























&lt;a class="anchor" href="#relaxing-lifetime-bounds" id="relaxing-lifetime-bounds"&gt;&lt;/a&gt;












&lt;a class="anchor" href="#sendness" id="sendness"&gt;&lt;/a&gt;




















&lt;a class="anchor" href="#afterword" id="afterword"&gt;&lt;/a&gt;</description><author>fasterthanli.me</author><pubDate>Wed, 25 Dec 2024 09:30:00 GMT</pubDate><guid isPermaLink="true">https://fasterthanli.me/articles/catching-up-with-async-rust</guid></item><item><title>My 2024 reads</title><link>https://www.alexanderlolis.com/my-2024-reads</link><description>Peopleware: Productive Projects and Teams</description><author>Alexander's Blog Blog</author><pubDate>Wed, 25 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.alexanderlolis.com/my-2024-reads</guid></item><item><title>My Favorite Christmas SNL Sketches</title><link>https://xavd.id/blog/post/my-favorite-christmas-snl-sketches/</link><description>A quick recap of the holiday sketches I come back to, year after year.&lt;br /&gt;&lt;br /&gt;&lt;a href="https://xavd.id/blog/post/my-favorite-christmas-snl-sketches/"&gt;Read the whole thing&lt;/a&gt;.</description><author>The David Brownman Blog</author><pubDate>Wed, 25 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xavd.id/blog/post/my-favorite-christmas-snl-sketches/</guid></item><item><title>A Cryptographically Secret Santa</title><link>https://specbranch.com/posts/cryptographic-santa/</link><description>&lt;p&gt;Twas about 4-6 weeks before Christmas, and all through the math department,
not a creature was stirring, not even a plucky young undergrad. Cryptography
professors Alice and Bob sat at the elliptically-curved conference table to plan
the department's secret Santa. Mallory, the department secretary, had been given
the task of organizing last year, and somehow managed to get three gifts while
leaving several people disappointed. This year's math department thus resolved
to do their secret Santa without a trusted party.&lt;/p&gt;</description><author>Speculative Branches</author><pubDate>Wed, 25 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://specbranch.com/posts/cryptographic-santa/</guid></item><item><title>How I manage my personal Fedora machine</title><link>https://blog.marbu.eu/posts/2024-12-25-my-fedora-setup</link><description>&lt;article&gt;
&lt;section class="article-header"&gt;
Posted on 25 December 2024

&lt;/section&gt;
&lt;section class="article-tags"&gt;
Tags: &lt;a href="/tags/Fedora.html" rel="tag" title="All pages tagged 'Fedora'."&gt;Fedora&lt;/a&gt;, &lt;a href="/tags/ansible.html" rel="tag" title="All pages tagged 'ansible'."&gt;ansible&lt;/a&gt;, &lt;a href="/tags/git.html" rel="tag" title="All pages tagged 'git'."&gt;git&lt;/a&gt;, &lt;a href="/tags/etckeeper.html" rel="tag" title="All pages tagged 'etckeeper'."&gt;etckeeper&lt;/a&gt;
&lt;/section&gt;
&lt;section&gt;
&lt;div class="article-toc"&gt;Table of Contents:
&lt;ul&gt;
&lt;li&gt;&lt;a href="#os-installation" id="toc-os-installation"&gt;OS Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#post-installation-setup-via-ansible" id="toc-post-installation-setup-via-ansible"&gt;Post Installation setup via Ansible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#etckeeper" id="toc-etckeeper"&gt;Etckeeper&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#backup" id="toc-backup"&gt;Backup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#upgrading-os" id="toc-upgrading-os"&gt;Upgrading OS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#reinstall-or-migration" id="toc-reinstall-or-migration"&gt;Reinstall or Migration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#disaster-recovery" id="toc-disaster-recovery"&gt;Disaster Recovery&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;p&gt;In this post I’m going to give a brief overview of how I manage my personal
Fedora laptop, from installation to upgrades and backups. This is indeed not
very original topic, but recently I discussed it with few people, and I
realized that having a reference to a post like this will be useful. And maybe
you will find some parts of it interesting.&lt;/p&gt;
&lt;!--more--&gt;
&lt;p&gt;Important assumption here is that that this approach is intended for a single
desktop machine I both own and use. If I had to manage a fleet of
desktops for other people, some choices here no longer makes sense.&lt;/p&gt;
&lt;h2 id="os-installation"&gt;&lt;a class="headerLink" href="#os-installation" title="OS Installation"&gt;OS Installation&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;First of all I need to install the operating system. I boot from &lt;a href="https://fedoraproject.org/spins/kde"&gt;Fedora KDE
Plasma&lt;/a&gt; live image and from there start
&lt;a href="https://docs.fedoraproject.org/en-US/quick-docs/anaconda-introduction/"&gt;Fedora installer&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The most important step during the installation is disk configuration.
I always use the whole disk erasing whatever was there, enable
&lt;a href="https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup"&gt;LUKS&lt;/a&gt; based
full disk encryption, select btrfs partitioning scheme and then let the
installer create a default configuration I can review and tweak as needed. I
insist on a separate volume for &lt;code&gt;/home&lt;/code&gt;, but sometimes I need other changes
such as separate volume for virtual machine images.
If I were not using btrfs, I would also want to control how much disk space is
left unused for later use.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Disk partitioning during Fedora 41 installation" src="/images/fedora41_anaconda_disk_partitioning.png" /&gt;
&lt;figcaption&gt;Disk partitioning during Fedora 41 installation&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;I don’t configure any additional user accounts, but I need to make sure root
account is enabled and set a password for it.&lt;/p&gt;
&lt;h2 id="post-installation-setup-via-ansible"&gt;&lt;a class="headerLink" href="#post-installation-setup-via-ansible" title="Post Installation setup via Ansible"&gt;Post Installation setup via Ansible&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After the installation I login via text console as root and install git and
ansible so that I can clone and run &lt;a href="https://gitlab.com/marbu/fedora-post-install"&gt;my ansible post installation
playbook&lt;/a&gt;. This way I perform
common boring post installation steps such as enabling
&lt;a href="https://rpmfusion.org/FAQ#Free_repository"&gt;rpmfusion free&lt;/a&gt;
repository, creating user accounts or installing additional rpm packages.&lt;/p&gt;
&lt;p&gt;This set of ansible playbook and roles serves both as a documentation
and as a scripted way to perform the setup. Ansible playbooks can be actually
quite readable if you care enough about that, and roles can include a README
file with references to documentation and reasoning behind the configuration.
Some of the roles I have there just implements a few steps taken from Fedora
documentation or wiki, such as the role
&lt;a href="https://gitlab.com/marbu/fedora-post-install/-/blob/master/roles/virtualization/tasks/main.yml?ref_type=heads"&gt;for kvm/libvirt based virtualization&lt;/a&gt;,
while others cover a personalized setup, like this
&lt;a href="https://gitlab.com/marbu/fedora-post-install/-/blob/master/roles/lightdm/tasks/main.yml?ref_type=heads"&gt;custom setup for a lightdm display manager&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;If you are considering this approach yourself, have a look at &lt;a href="https://linux-system-roles.github.io/"&gt;Linux System
Roles&lt;/a&gt; project (btw &lt;a href="https://packages.fedoraproject.org/pkgs/linux-system-roles/linux-system-roles/"&gt;Fedora has it
packaged&lt;/a&gt;).
They provide set of general ansible roles you can parametrize via set of
variables passed to it. This can save you some time spent on implementing and
testing a role. I don’t use it in my post install playbook myself since I
created my playbook way before this project started.&lt;/p&gt;
&lt;p&gt;And even though the work I put into this is indeed bit higher compared to a
case when I just do them manually and then write it down somewhere,
I don’t need to reinstall a machine again to get the time
and effort invested into this back. Just by rerunning the playbook I can
check (and enforce) whether the configuration I did long time ago is still
used. Sometimes I just tweak or create
playbook/role to reconfigure something long after the installation, or I use
some of the roles in a different context, eg. for setting up a testing virtual
machine. That said, it’s worth noting that because of changes in the
distribution and ansible ecosystem itself, additional maintenance is needed
every now and then.&lt;/p&gt;
&lt;h2 id="etckeeper"&gt;&lt;a class="headerLink" href="#etckeeper" title="Etckeeper"&gt;Etckeeper&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;That said I don’t use ansible for everything. In most cases I just edit config
files in &lt;code&gt;/etc&lt;/code&gt; directly (or use tools like &lt;code&gt;firewall-cmd&lt;/code&gt; which
edits files there on my behalf). Only if I conclude that the automation
effort makes sense, eg. because I will need given system change next time I
reinstall the system, I sit down to write ansible playbook or role for it.&lt;/p&gt;
&lt;p&gt;This means that I still need to keep &lt;code&gt;/etc&lt;/code&gt; organized, which I manage by
tracking it with git using &lt;a href="https://etckeeper.branchable.com/"&gt;etckeeper&lt;/a&gt;.
After I change something in &lt;code&gt;/etc&lt;/code&gt; directory, I
create a dedicated commit for the change, including a description of what I’m
doing in a commit message. This way I know what, when and why I have changed
something.
An important piece of etckeeper tools is a hook for a package manager, so that
any change made in &lt;code&gt;/etc&lt;/code&gt; during package installation, removal or update is
captured in a dedicated commit along with all relevant metadata (list of
packages, versions …).
The configuration I use is available in my
&lt;a href="https://gitlab.com/marbu/fedora-post-install/-/blob/master/roles/etckeeper/tasks/main.yml?ref_type=heads"&gt;etckeeper ansible role&lt;/a&gt;,
but in short, I disable daily auto commits forcing myself to commit any changes
explicitly.&lt;/p&gt;
&lt;p&gt;But using both ansible as well as etckeeper creates one problem: how to
commit changes done during run of a playbook? Making one big commit for the
whole run would be easy, but it will result in a commit too large to be useful.
To solve this I wrote a simple
&lt;a href="https://gitlab.com/marbu/fedora-post-install/-/blob/master/callback_plugins/etckeeper.py?ref_type=heads"&gt;etckeeper ansible callback plugin&lt;/a&gt;
hooking ansible playbook result callbacks to create commit for each ansible
task in a playbook.
The only downside of this approach is that it works locally only, but it’s not
a problem for my use case here.&lt;/p&gt;
&lt;p&gt;Overall this approach has multiple benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is a clear record of all configuration changes with additional
metadata.&lt;/li&gt;
&lt;li&gt;Based on a commit message I can see if it comes from a default configuration,
was done via my ansible playbook or manually.&lt;/li&gt;
&lt;li&gt;I can use standard git tools (like &lt;code&gt;git blame&lt;/code&gt;, &lt;code&gt;git log&lt;/code&gt;
or &lt;a href="https://github.com/k4rthik/git-cal"&gt;&lt;code&gt;git cal&lt;/code&gt;&lt;/a&gt;) to get more insight.&lt;/li&gt;
&lt;li&gt;When I run into a problem, I can have a look what I changed recently and
recheck if that can be related.&lt;/li&gt;
&lt;li&gt;Since easy fallback via &lt;code&gt;git checkout&lt;/code&gt; or &lt;code&gt;git revert&lt;/code&gt; is available,
I can experiment with a configuration without having to manually backup
existing state.&lt;/li&gt;
&lt;li&gt;I will notice unexpected changes performed automatically by some system
component (I can’t miss this since it will leave uncommited changes)
or can inspect changes which comes with new version of a package.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Another benefit of this approach is that it provides a convenient way to
archive and review old system configurations. I used to create tarballs of
&lt;code&gt;/etc&lt;/code&gt; directory just in case I may need it later, but I almost
never used it for anything, even when I had some reason to do so. This is
because the tarball
approach lacks context (What was the thing I changed compared to the default
and why? Does it actually contain the change I’m interested in?) and
standardization (Where do I actually store it?).
Etckeeper solves all this. I can just push
current state of main branch into separate git bare repository.
Later on, when I want to revisit it on a different system, I can just fetch
this archive repository as a remote in existing etckeeper managed repo, and
then I’m able to work with it easily. For example (assuming name of the archive
remote is &lt;code&gt;dione-2018&lt;/code&gt;):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List commits in the archive remote: &lt;code&gt;git log --graph --oneline   dione-2018/master&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Compare the archived state with the current one for a given file, eg.:
&lt;code&gt;git diff dione-2018/master HEAD dnf/dnf.conf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Use particular old commit in the current &lt;code&gt;/etc&lt;/code&gt; repo via &lt;code&gt;git cherry-pick&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="backup"&gt;&lt;a class="headerLink" href="#backup" title="Backup"&gt;Backup&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I backup whole &lt;code&gt;/home&lt;/code&gt; directory. On a personal machine, I don’t really care
about anything else, with exception of &lt;code&gt;/etc&lt;/code&gt; directory, for which I maintain
etckeeper remote bare repo in &lt;code&gt;/home&lt;/code&gt; dir.
This way, I don’t need to think about what is crucial enough for a backup,
everything in &lt;code&gt;/home&lt;/code&gt; dir is! And it also makes recovery
planning simple. Other backup schemes can be used on top or along side of it,
but that is out of scope of this post.&lt;/p&gt;
&lt;p&gt;This approach relies on the fact that I use dedicated home volume and a
partitioning scheme which allows for quick
&lt;a href="https://en.wikipedia.org/wiki/Copy-on-write#In_computer_storage"&gt;copy-on-write&lt;/a&gt;
snapshots so that I
can make a shapshot instantly and then continue using the machine while a
backup is running from a consistent state. This is helpful especially when
the backup itself takes longer because of network or iops bottleneck on the
receiving end.&lt;/p&gt;
&lt;p&gt;At first I used
&lt;a href="/posts/2023-08-02-btrfs-backup#my-old-backup-scheme"&gt;lvm thin pools&lt;/a&gt;, but few
years ago I moved to btrfs, so that
now I can use btrfs send/receive functionality to transfer incremental
snapshots to a local or remote backup device. Here I can run btrfs scrub to
make sure that I still have the data.&lt;/p&gt;
&lt;p&gt;To &lt;a href="/posts/2023-08-02-btrfs-backup#restoring-the-home-volume-properly"&gt;restore data, I will just use send/receiver
again&lt;/a&gt;, but
this time transferring data from the backup device to the machine.&lt;/p&gt;
&lt;h2 id="upgrading-os"&gt;&lt;a class="headerLink" href="#upgrading-os" title="Upgrading OS"&gt;Upgrading OS&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I need to upgrade to a new Fedora release every half a year or so. But since
&lt;a href="https://docs.fedoraproject.org/en-US/releases/lifecycle/"&gt;a Fedora release is supported for about 13
months&lt;/a&gt;, I’m not
forced to upgrade until I can find some time to actually do it.&lt;/p&gt;
&lt;p&gt;Nowadays I use Fedora supported
&lt;a href="https://docs.fedoraproject.org/en-US/quick-docs/upgrading-fedora-offline/"&gt;dnf system-upgrade&lt;/a&gt;.
This means that the upgrade is handled by the package manager. First it
downloads all new rpm packages and runs dnf transaction check on them (so that
one can detect a package conflict before the upgrade), and then if all looks
good one reboots into system-update
&lt;a href="https://www.freedesktop.org/software/systemd/man/latest/systemd.target.html"&gt;systemd target&lt;/a&gt;
to perform the upgrade in an isolated environment.&lt;/p&gt;
&lt;p&gt;I used &lt;a href="https://fedoraproject.org/wiki/Upgrading_Fedora_using_package_manager"&gt;yum/dnf based
upgrade&lt;/a&gt;
long &lt;a href="https://funnelfiasco.com/blog/2010/11/15/upgrading-to-fedora-14-with-yum/"&gt;before this was
polished&lt;/a&gt;
into an official upgrade path, since I always preferred this approach over
other options. Reinstalling the system just to upgrade a personal machine
doesn’t feel reasonable to me.&lt;/p&gt;
&lt;h2 id="reinstall-or-migration"&gt;&lt;a class="headerLink" href="#reinstall-or-migration" title="Reinstall or Migration"&gt;Reinstall or Migration&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now you may wonder why do I mention reinstall when I just noted that I use dnf
based upgrade. But in case of significant hardware changes (buying a whole new
machine or just replacing small or dying disk with a new one) I find it easier
and faster to just reinstall the system from scratch, assuming that the OS
installer will detect the new hardware and set appropriate defaults.
The same applies to a significant changes in storage configuration or
partitioning. You may be thinking that there is no reason to change storage
setup so drastically, but I did that few times, eg. when I
wanted to reencrypt my LUKS volume using latest defaults or moved from lvm thin
pools to btrfs.&lt;/p&gt;
&lt;p&gt;And thanks to my ansible post installation automation and backups of home
volume, this process boils down to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;making backup of home volume&lt;/li&gt;
&lt;li&gt;(re)installing the system&lt;/li&gt;
&lt;li&gt;post install setup via ansible playbook&lt;/li&gt;
&lt;li&gt;recovery of home volume backup snapshot&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Obviously this requires much more effort compared to the sheer dnf upgrade, but
it’s manageable since it’s documented/automated. Most effort usually goes into
considering new hardware or software configurations or maintenance of ansible
code as I noted already.&lt;/p&gt;
&lt;h2 id="disaster-recovery"&gt;&lt;a class="headerLink" href="#disaster-recovery" title="Disaster Recovery"&gt;Disaster Recovery&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let’s say a disk in my machine stops working out of sudden so that I need to
buy a new one and then restore it into working condition as soon as possible.
And as you have probably noticed, I have all the pieces in place to handle this
via reinstall followed by a full backup recovery as explained above.
Moreover since I reinstall my machine every now and then, I have the recovery
procedure fully tested in real conditions. And yes, I don’t do such a test
very often, it’s still way better than trying it out for the first time
while recovering a machine under stress. The last thing I want to
run into is that something doesn’t actually work as I thought it would.&lt;/p&gt;

&lt;/section&gt;
&lt;/article&gt;

&lt;div class="post-end-note"&gt;
&lt;p&gt;Do you have a comment or question? You can &lt;a href="/about.html"&gt;contact me&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;</description><author>blog.marbu.eu</author><pubDate>Wed, 25 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.marbu.eu/posts/2024-12-25-my-fedora-setup</guid></item><item><title>I have failed.  Now what?</title><link>https://blog.rongarret.info/2024/12/i-have-failed-now-what.html</link><description>Nearly two months ago now I wrote:It's getting harder and harder to find a reason to keep doing this.&amp;nbsp; My 
opportunity costs are high, and writing a blog entry takes a non-trivial
 amount of time.&amp;nbsp; I wrote this because I needed to blow off some steam, 
and I wanted to get my position on the election results on the record 
while they were still fresh in my mind.&amp;nbsp; Whether I keep</description><author>Rondam Ramblings</author><pubDate>Tue, 24 Dec 2024 20:41:35 GMT</pubDate><guid isPermaLink="true">https://blog.rongarret.info/2024/12/i-have-failed-now-what.html</guid></item><item><title>Practical Deep Learning, Lesson 7, Movie Recommendations</title><link>https://www.danielcorin.com/til/fastai/lesson7-movie-recommendations/</link><description>Practical Deep Learning, Lesson 7, Movie Recommendations</description><author>Thought Eddies</author><pubDate>Tue, 24 Dec 2024 20:33:43 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/fastai/lesson7-movie-recommendations/</guid></item><item><title>Elon Musk</title><link>https://ananthmajumdar.substack.com/p/elon-musk</link><description>Some interesting principles I learnt from Walter Issacson&amp;#8217;s Elon Musk biography</description><author>Ananth's Reflections</author><pubDate>Tue, 24 Dec 2024 16:40:08 GMT</pubDate><guid isPermaLink="true">https://ananthmajumdar.substack.com/p/elon-musk</guid></item><item><title>Bonkoski University: Fall/Winter Semester 2024</title><link>https://www.xorvoid.com/bonkoski_university.html</link><description>Bonkoski University: Fall/Winter Semester 2024</description><author>xorvoid</author><pubDate>Tue, 24 Dec 2024 13:46:09 GMT</pubDate><guid isPermaLink="true">https://www.xorvoid.com/bonkoski_university.html</guid></item><item><title>Pareto Hobbies 2025</title><link>https://jodavaho.io/posts/pareto-hobbies-2025.html</link><description>&lt;h1 id="pareto-hobbies-2025"&gt;Pareto Hobbies 2025&lt;/h1&gt;
&lt;p&gt;I decided to plot possible hobbies in 2025 in terms of career potential and personal enjoyment.&lt;/p&gt;
&lt;p&gt;This is a silly thing to do, but it is also a uniquely software-engineering
problem to have - for some reason we seem to be prone to lots of extra
curricular learning, quantified self, and a constant need to optimize ourselves
for our employability.&lt;/p&gt;
&lt;p&gt;As a computer scientist and sometimes hardware hacker, I&amp;rsquo;m most interested in the following things:&lt;/p&gt;
&lt;h2 id="geospatial-ml"&gt;Geospatial ML&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m fascinated by taking spatial data and running predictors on it. This could be predicitng fine-grained weather patterns, predicting traffic, etc.&lt;/p&gt;
&lt;p&gt;At &lt;a href="https://flightscience.ai"&gt;flight science&lt;/a&gt;, this has lots of potential applications, so the usefulness is fairly high. I&amp;rsquo;ve also found that &lt;a href="https://josh.vanderhook.info/publications.html#j2014precag"&gt;kriging is very useful for robotics&lt;/a&gt; though it has been 10 years since I&amp;rsquo;ve applied it.&lt;/p&gt;
&lt;h2 id="quantum-computing"&gt;Quantum Computing&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://blog.google/technology/research/google-willow-quantum-chip/"&gt;recent google announcement&lt;/a&gt; has reinvigorated my old interest in quantum computing. A friend from JPL who I highly respect has said QC is at the equivalent of the vaccuum tube era of computing.&lt;/p&gt;
&lt;p&gt;Given we&amp;rsquo;re only now demonstrating benchmark problems, and that most the
researchers in those areas are probably hardware and quantum PhDs &amp;hellip;. well,
I&amp;rsquo;m a bigshot PhD Computer Scientist, but doubt I could contribute to the field without years of retraining.&lt;/p&gt;
&lt;p&gt;Relevance: Very low.&lt;/p&gt;
&lt;h2 id="radar-processing"&gt;Radar Processing&lt;/h2&gt;
&lt;p&gt;Super-specific interest, but I want to use and SDR to build a homebrew radar and track boats at the in-laws lake cabin. Why? Who knows.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m super comfortable with tracking and estimation, but would need to learn how to extract a &amp;ldquo;measurement&amp;rdquo; from the timing and Doppler shift of the radar return.&lt;/p&gt;
&lt;p&gt;Given I could see myself working in tracking and estimation in the future, this could be a useful skill.&lt;/p&gt;
&lt;h2 id="aws-code-as-infra"&gt;AWS code as infra&lt;/h2&gt;
&lt;p&gt;Not very glamorous, but I hate the AWS console and would like to be able to do everything in code.&lt;/p&gt;
&lt;h2 id="rl-for-planning"&gt;RL for planning&lt;/h2&gt;
&lt;p&gt;This is something that is near and dear to my heart. I have been working in planning systems for a while, and have used &amp;ldquo;RL&amp;rdquo; (POMDP, etc) in the past, but only as a tangential part of the team.&lt;/p&gt;
&lt;p&gt;More generally, the use of end-to-end learned systems for &lt;em&gt;control&lt;/em&gt; at least seems to be an increasingly dominant way of doing business.
With RL+Planning, my goal is to eventually be able to tradeoff classical
estimation and planning with end-to-end systems, and use the best parts of
either for a better-than-either pipeline. At the jobs I&amp;rsquo;ve had I&amp;rsquo;ve seen a
weird false dichotomy that produces decent results once committed, but there&amp;rsquo;s
always this tension that &amp;ldquo;the other way&amp;rdquo; is better, producing weird politics.&lt;/p&gt;
&lt;p&gt;Most recently, a classical planning pipeline was completely broken by an
ML-based estimation of orientation of obstacles. On inspection they were using
quite possibly the worst estimator you could use, and ignoring all kinds of
good data, because (I think) they were counting on perfect &amp;ldquo;measurements&amp;rdquo; from
the ML-based vision system. That kind of thing must happen all the time - get
stuck in a local minimum of &amp;ldquo;tune just a little better&amp;rdquo; when a good filter on
top of a noisy estimator could solve all your problems. There&amp;rsquo;s probably a
million such examples in planning systems too.&lt;/p&gt;
&lt;p&gt;For discrete, well structured problems with noisy data on them, such as we
might encounter in &lt;a href="https://flightscience.ai"&gt;flight science&lt;/a&gt;, I think there&amp;rsquo;s
a lot of potential for RL to be extremely useful.&lt;/p&gt;
&lt;h2 id="deep-learning--large-language-models"&gt;Deep Learning / Large Language Models&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m not super interested in this, but it seems like a good thing to know.&lt;/p&gt;
&lt;h1 id="ratings"&gt;Ratings&lt;/h1&gt;
&lt;p&gt;My interest and career potential vary. I&amp;rsquo;ve rated them on a scale of 1-100, with 100 being the highest.&lt;/p&gt;
&lt;p&gt;This is a stupid way to determine hobbies, but I&amp;rsquo;m doing it anyway.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Item&lt;/th&gt;
          &lt;th&gt;Career relevance&lt;/th&gt;
          &lt;th&gt;Personal Interest&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;RL for planning&lt;/td&gt;
          &lt;td&gt;50&lt;/td&gt;
          &lt;td&gt;40&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;DL/LLM in general&lt;/td&gt;
          &lt;td&gt;20&lt;/td&gt;
          &lt;td&gt;35&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Quantum Computing&lt;/td&gt;
          &lt;td&gt;0&lt;/td&gt;
          &lt;td&gt;90&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;AWS Code as infra&lt;/td&gt;
          &lt;td&gt;60&lt;/td&gt;
          &lt;td&gt;10&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Radar / signal processing / tracking&lt;/td&gt;
          &lt;td&gt;20&lt;/td&gt;
          &lt;td&gt;80&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Geospatial ML, kriging, weather data&lt;/td&gt;
          &lt;td&gt;80&lt;/td&gt;
          &lt;td&gt;40&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h1 id="results"&gt;Results&lt;/h1&gt;
&lt;p&gt;Here&amp;rsquo;s the plotted results:&lt;/p&gt;
&lt;p&gt;&lt;img alt="pareto hobbies 2025" src="https://jodavaho.io/img/interests_plotted.png" /&gt;&lt;/p&gt;
&lt;p&gt;How do we make a decision on this data? As a planning guy, I should be able to do this!&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll do a &lt;a href="https://en.wikipedia.org/wiki/Pareto_principle"&gt;pareto analysis&lt;/a&gt;, using the reward function of &lt;code&gt;||(career_relevance , personal_interest)||&lt;/code&gt; (i.e., magnitude of the vector).&lt;/p&gt;
&lt;p&gt;So, let&amp;rsquo;s plot the pareto frontier for each:&lt;/p&gt;
&lt;p&gt;&lt;img alt="pareto hobbies 2025" src="https://jodavaho.io/img/interests_dominated.png" /&gt;&lt;/p&gt;
&lt;p&gt;In this case, the outermost circle is the &amp;ldquo;best&amp;rdquo;, and a solution &amp;ldquo;dominates&amp;rdquo; anything inside its circle.
So, according to this, I should focus on Quantum Computing and Geospatial ML. If I had applied scalars to the career relevance and personal interest, those circles would have been ellipses, and the dominance ordering would change. Maybe later.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Tue, 24 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/posts/pareto-hobbies-2025.html</guid></item><item><title>100 Page Python Intro book announcement</title><link>https://learnbyexample.github.io/100-page-python-intro-book-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;100 Page Python Intro&lt;/strong&gt; ebook. This book is a short, introductory guide for the Python programming language. This book is well suited:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As a reference material for Python beginner workshops&lt;/li&gt;
&lt;li&gt;If you have prior experience with another programming language&lt;/li&gt;
&lt;li&gt;If you want a complement resource after reading a Python basics book, watching a video course, etc&lt;/li&gt;
&lt;/ul&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of the ebook for FREE till 02-Jan-2025. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/100pagepythonintro/c/YearEndSale"&gt;https://leanpub.com/100pagepythonintro/c/YearEndSale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/100pagepythonintro"&gt;https://learnbyexample.gumroad.com/l/100pagepythonintro&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two of my bundles are on sale as well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;All 13 Books Bundle&lt;/strong&gt; — $15 (normal price $32), learn Regular Expressions, Linux CLI tools, Python, Vim and more!
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/learnbyexample-all-books/c/YearEndSale"&gt;Leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/all-books/YearEndSale"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Awesome Regex Bundle&lt;/strong&gt; — $10 (normal price $20), Python, Ruby, JavaScript, BRE/ERE, PCRE and Vim regular expressions
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/regex/c/YearEndSale"&gt;Leanpub&lt;/a&gt; or &lt;a href="https://learnbyexample.gumroad.com/l/regex/YearEndSale"&gt;Gumroad&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Python version updated to &lt;strong&gt;3.13.0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Added more exercises and you can now practice some of them using this &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PythonExercises"&gt;interactive TUI app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Descriptions and external links were updated/corrected&lt;/li&gt;
&lt;li&gt;Updated Acknowledgements section&lt;/li&gt;
&lt;li&gt;Code snippets related to info/warning sections will now appear as a single block&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;li&gt;Images centered for EPUB format&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;Check out my &lt;a href="https://learnbyexample.github.io/tips/"&gt;programming tips&lt;/a&gt; covering Python, command line tools and Vim:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PlFDiH3FXTHXRbhWs2sB3F"&gt;Python tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4PNTmRqZBSUgKaiHbRL2zeY"&gt;Linux command line tips&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos"&gt;Vim tips&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;It's very thorough, written with care, and presented in a way that makes sense. Even as an intermediate Python programmer, I found use in this book.&lt;/p&gt;
&lt;p&gt;— feedback by &lt;a href="https://healeycodes.com/"&gt;Andrew Healey&lt;/a&gt; on an early draft of &amp;quot;100 Page Python Intro&amp;quot; mentioned in &lt;a href="https://news.ycombinator.com/item?id=26082464"&gt;this Hacker News thread&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="interactive-tui-app"&gt;Interactive TUI app&lt;a class="zola-anchor" href="#interactive-tui-app"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I also wrote an &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PythonExercises"&gt;interactive TUI app&lt;/a&gt; based on some of the exercises from the ebook. Reference solutions are also provided.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot from the interactive TUI app for Python exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PythonExercises/python_exercises.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Numeric data types&lt;/li&gt;
&lt;li&gt;Strings and user input&lt;/li&gt;
&lt;li&gt;Defining functions&lt;/li&gt;
&lt;li&gt;Control structures&lt;/li&gt;
&lt;li&gt;Importing and creating modules&lt;/li&gt;
&lt;li&gt;Installing modules and Virtual environments&lt;/li&gt;
&lt;li&gt;Exception handling&lt;/li&gt;
&lt;li&gt;Debugging&lt;/li&gt;
&lt;li&gt;Testing&lt;/li&gt;
&lt;li&gt;Tuple and Sequence operations&lt;/li&gt;
&lt;li&gt;List&lt;/li&gt;
&lt;li&gt;Mutability&lt;/li&gt;
&lt;li&gt;Dict&lt;/li&gt;
&lt;li&gt;Set&lt;/li&gt;
&lt;li&gt;Text processing&lt;/li&gt;
&lt;li&gt;Comprehensions and Generator expressions&lt;/li&gt;
&lt;li&gt;Dealing with files&lt;/li&gt;
&lt;li&gt;Executing external commands&lt;/li&gt;
&lt;li&gt;Command line arguments&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/100_page_python_intro/introduction.html"&gt;https://learnbyexample.github.io/100_page_python_intro/introduction.html&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;https://github.com/learnbyexample/100_page_python_intro&lt;/a&gt; for programs, example files, markdown source and other details about the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;a class="zola-anchor" href="#feedback"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/100_page_python_intro/issues"&gt;https://github.com/learnbyexample/100_page_python_intro/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 24 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/100-page-python-intro-book-announcement/</guid></item><item><title>The True Cost of PMI: Why you should pay down your low-interest mortgage</title><link>https://andrewbridges.org/the-true-cost-of-pmi/</link><description>I got a guaranteed return of 10.66% by paying down my low-interest mortgage to eliminate PMI.</description><author>Show Notes</author><pubDate>Mon, 23 Dec 2024 23:24:30 GMT</pubDate><guid isPermaLink="true">https://andrewbridges.org/the-true-cost-of-pmi/</guid></item><item><title>Lessons from Cybersyn</title><link>https://magis.substack.com/p/lessons-from-cybersyn</link><description>What I learned from running a DaaS startup for two years.</description><author>Magis</author><pubDate>Mon, 23 Dec 2024 16:03:33 GMT</pubDate><guid isPermaLink="true">https://magis.substack.com/p/lessons-from-cybersyn</guid></item><item><title>Hello emacs.tv</title><link>https://xenodium.com/hello-emacstv</link><description>&lt;p&gt;A few days ago, &lt;a href="https://sachachua.com/blog/"&gt;Sacha Chua&lt;/a&gt; mentioned how &lt;a href="https://social.sachachua.com/@sacha/statuses/01JF94JQQNNRXMTKN3Y1774TFP"&gt;cool it would be to have an Emacs video index&lt;/a&gt; like &lt;a href="https://www.rubyvideo.dev/topics"&gt;Ruby Video&lt;/a&gt;. I mentioned how I had similarly considered a low-tech solution, maybe powered by plain text (bonus points for &lt;a href="https://orgmode.org/"&gt;org mode&lt;/a&gt; of course).&lt;/p&gt;
&lt;p&gt;A little later, Sacha &lt;a href="https://social.sachachua.com/@sacha/statuses/01JFG5T3C6E88362DRDZN9ANA6"&gt;shared a preliminary video feed dump&lt;/a&gt;, in org! With that, I wrote the &lt;a href="https://indieweb.social/@xenodium/113682069315989397"&gt;first experiment to render the org feed&lt;/a&gt; and &lt;a href="https://emacs.tv/"&gt;emacs.tv&lt;/a&gt; was born.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/hello-emacstv/screenshot.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://emacs.tv/"&gt;emacs.tv&lt;/a&gt; is merely a few days old. Powered by an org feed (rendered client-side), but we can fetch and render in all sorts of ways. &lt;a href="https://emacs.tv/"&gt;emacs.tv&lt;/a&gt; brings it to the web, though I'm sure we can come up with all sorts of Emacs integrations. A new major mode? Or maybe convert the org feed to rss and plug into &lt;a href="https://github.com/skeeto/elfeed"&gt;elfeed&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;This is what a feed entry looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-org"&gt;* EmacsConf.org: How we use Org Mode and TRAMP to organize and run a multi-track conference :emacsconf:emacsconf2023:org:tramp:
:PROPERTIES:
:DATE: 2023-12-03
:URL: https://emacsconf.org/2023/talks/emacsconf
:MEDIA_URL: https://media.emacsconf.org/2023/emacsconf-2023-emacsconf--emacsconforg-how-we-use-org-mode-and-tramp-to-organize-and-run-a-multitrack-conference--sacha-chua--main.webm
:YOUTUBE_URL: https://www.youtube.com/watch?v=uTregv3rNl0
:TOOBNIX_URL: https://toobnix.org/w/eX2dXG3xMtUHuuBz4fssGT
:TRANSCRIPT_URL: https://media.emacsconf.org/2023/emacsconf-2023-emacsconf--emacsconforg-how-we-use-org-mode-and-tramp-to-organize-and-run-a-multitrack-conference--sacha-chua--main.vtt
:SPEAKERS: Sacha Chua
:SERIES: EmacsConf 2023
:END:
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;We need your help&lt;/h2&gt;
&lt;p&gt;As mentioned, this is a new project. It's a good start, but it can only get better with your help.&lt;/p&gt;
&lt;h3&gt;Submit more content&lt;/h3&gt;
&lt;p&gt;Sacha kickstarted a &lt;a href="https://raw.githubusercontent.com/emacstv/emacstv.github.io/refs/heads/main/videos.org"&gt;wonderful video feed,&lt;/a&gt; a collection of 1715 videos as of today. We need more. Are your published videos missing? Reckon other videos should be listed? Please help by &lt;a href="https://github.com/emacstv/emacstv.github.io#add-videos"&gt;submitting&lt;/a&gt; new entries.&lt;/p&gt;
&lt;h3&gt;Improve our tagging&lt;/h3&gt;
&lt;p&gt;Many of the listed videos could use more tags. Please help us by tagging content in &lt;a href="https://raw.githubusercontent.com/emacstv/emacstv.github.io/refs/heads/main/videos.org"&gt;video.org&lt;/a&gt; and submit a &lt;a href="https://github.com/emacstv/emacstv.github.io/pulls"&gt;pull request&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Take it for a spin&lt;/h3&gt;
&lt;p&gt;Or maybe just take &lt;a href="https://emacs.tv/"&gt;emacs.tv&lt;/a&gt; for a spin and &lt;a href="https://github.com/emacstv/emacstv.github.io/issues"&gt;give us some feedback&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy holidays! 🎄☃️&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Mon, 23 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/hello-emacstv</guid></item><item><title>Robotics Predictions for 2025</title><link>https://ben.bolte.cc/posts/2024-12-23-predictions</link><description>Some predictions for how the robotics landscape looks next year, with varying degrees of confidence.</description><author>Ben Bolte</author><pubDate>Mon, 23 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ben.bolte.cc/posts/2024-12-23-predictions</guid></item><item><title>Uncapped notes dont work for first rounds.</title><link>https://klinger.io/posts/uncapped-rounds</link><description>Uncapped safe notes misalign incentives in first rounds… let me explain why</description><author>Blog posts of Andreas Klinger</author><pubDate>Mon, 23 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://klinger.io/posts/uncapped-rounds</guid></item><item><title>From HTML template strings to elements</title><link>https://ricardoanderegg.com/posts/from-html-template-strings-to-elements/</link><description>&lt;p&gt;Some time ago, I found (and used for a while) the &lt;a href="https://github.com/observablehq/htl"&gt;htl&lt;/a&gt; library. Before that, I was using raw &lt;a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals"&gt;template literals&lt;/a&gt; to build HTML, and then setting some &lt;code&gt;element.innerHTML = newString&lt;/code&gt;. &lt;code&gt;htl&lt;/code&gt; uses template literals, but instead returns a regular DOM Node object. This makes it a lot easier to add even listeners, setting other properties, and overall manipulating the object before appending it to the current document.&lt;/p&gt;
&lt;p&gt;I was curious to see how it was done, and it turns out it&amp;rsquo;s easier than I thought. The full library is a single &lt;a href="https://github.com/observablehq/htl/blob/e3e9777d09f7b5aea3ebc52e73b86a1246dcab7e/src/index.js"&gt;index.js&lt;/a&gt; file. Most of the code is parsing the HTML string to support different features from their &amp;ldquo;templating-format&amp;rdquo; (or that&amp;rsquo;s what I understood), which allows doing things like:&lt;/p&gt;</description><author>Posts on rand[om]</author><pubDate>Mon, 23 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ricardoanderegg.com/posts/from-html-template-strings-to-elements/</guid></item><item><title>Learn new skills</title><link>https://www.attejuvonen.fi/learn/</link><description>Great learning materials are rare. This is a collection of gems I’ve encountered over the years, presented as a 90’s style web link list…</description><author>TODO</author><pubDate>Mon, 23 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.attejuvonen.fi/learn/</guid></item><item><title>Feel, don't think</title><link>https://ntietz.com/blog/feel-dont-think/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;I started playing clarinet in fourth grade, and dabbled in other instruments..
I never stopped entirely, but I took years-long breaks from playing music.&lt;/p&gt;
&lt;p&gt;Earlier this year, I picked up a new instrument while I was very sick.
I got a &lt;a href="https://en.wikipedia.org/wiki/Wind_controller"&gt;wind synth&lt;/a&gt;, the Roland AE-20.
It was a somewhat impulsive purchase, in the moment, but I'd been wanting one for a while—the question was just when.&lt;/p&gt;
&lt;p&gt;Getting back into music at &lt;em&gt;this&lt;/em&gt; particular moment felt... irresponsible.
I was having trouble working, or even walking around our block.
Yet there was this magnetic pull toward this instrument, toward making music again.
Music pulled me back in my hardest hour.&lt;/p&gt;
&lt;p&gt;Almost immediately, it helped me move through my illness.
When my heart rate shot up, playing scales and simple melodies could rein it in.
When I couldn't calm down, playing long tones and scales and silly sounds would make me feel better.
And the entire recovery process was helped by having a small low-stakes goal, like learning a simple Bach piece.&lt;/p&gt;
&lt;p&gt;As I progressed through my recovery, I had a few moments where things clicked into place for me.
One day I suddenly connected the fingerings to those of the clarinet, and was able to play much more fluently.
Another day, I'd figure out the phrasing of one of the pieces I was working on.&lt;/p&gt;
&lt;p&gt;But I struggled with a lot of the things that I struggled with before: rhythms, expression, you know... &lt;em&gt;music&lt;/em&gt;.
I want to express myself better through playing and, eventually, composing music.
If I wanted to get there, I'd need a teacher.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;I found a teacher through a piano teacher in town.
I asked if she knew any saxophone teachers, since my instrument uses sax fingerings, especially someone who also plays jazz.
She connected me with Charles, a local musician who's served as principal clarinet in some esteemed ensembles.
So I reached out, asking him if he'd teach me on this unusual instrument.&lt;/p&gt;
&lt;p&gt;Charles teaches clarinet and saxophone, not wind synth.
I'm the first student he's had who uses an amplifier.
But the instrument uses sax fingerings, and it responds to breath the way an acoustic woodwind does, so he figured he can probably help me.
We decided to try it.&lt;/p&gt;
&lt;p&gt;Right away, his method started shaping how I approach music, for the better.
He's not focused too much on the specific instrument, but on the rhythm and expression you're putting through it.
Obviously the instrument itself matters, but not as much as I'd once thought.
I have improved my technique, but not at the cost of expressivity, and I'm losing the mechanical approach I used to take.&lt;/p&gt;
&lt;p&gt;He had me buy a few books.
I got the Rubank Elementary Method for saxophone, which we're working through to shore up my fundamentals and build on expression.
It's going much faster than when I worked through the same volumes for clarinet in school.
I picked up a staff paper notebook so he can write out exercises for me.
And he had me pick up Robert Starer's Rhythmic Training.
That book has been the cornerstone of our work together and has challenged me, showed me I can learn hard things that feel impossible, and fundamentally altered how I look at my art and writing.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;At the same time I started working with Charles, I was in a writing class from &lt;a href="https://www.corporealwriting.com/"&gt;Corporeal Writing&lt;/a&gt;.
It was tremendously challenging for me, and I took it based on a friend's recommendation.
The basis of the class is to write from your body, not your brain.
I'm positive I don't understand a lot of things from the class still, but it showed me a different way of writing.&lt;/p&gt;
&lt;p&gt;In school, my approach to writing was fact-based.
For non-fiction, I'd lay out all the facts in my head, or the chronological storyline.
Then I'd write that in some narrative, mapping out the logical ordering.
It ended up... technical.
And fiction took a similar approach, just that the facts and story were things I came up with.
But anything nonlinear, or imagery that's not literal, evaded me.&lt;/p&gt;
&lt;p&gt;The first week of my class put cracks in that for me.
By the end, it had completely opened up the possibilities in my writing.&lt;/p&gt;
&lt;p&gt;Our first prompt was challenging for me to understand.
But I embraced that difficulty and sat with it, listening to my body, writing what came to me and what I felt from the prompt instead of what I thought it meant.
That process was difficult and, at times, painful.
The first assigment brought me to places from my past that I'd pushed down.
I cried from the act of writing.&lt;/p&gt;
&lt;p&gt;That never happened before.&lt;/p&gt;
&lt;p&gt;By the end of the class, I was able to produce imagery that made my classmates &lt;em&gt;feel&lt;/em&gt; things, instead of &lt;em&gt;think&lt;/em&gt; them.
I could write from my feelings.
It didn't get less painful, so it's not something I can do all the time—but that it's possible is not something I knew at all.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;At our first or second lesson, we started working through the rhythm book.
The first section we focused on features syncopation.
My stomach dropped when we got there, since this is something I've always had trouble with.&lt;/p&gt;
&lt;p&gt;But in the week after that lesson, I got to where I could feel a syncopated beat for the first time.
I've &lt;em&gt;played&lt;/em&gt; them before, but it never felt natural and it was often from following someone else's cues.
The practice I did let me get to where I can produce syncopation reliably.&lt;/p&gt;
&lt;p&gt;The feeling was intoxicating.
The moment that syncopation embedded itself into my brain, I filled with warmth and confidence.
I relaxed.
I couldn't stop doing those exercises, to feel that again.&lt;/p&gt;
&lt;p&gt;And so the next challenge arrived: triplets.
This was, yet again, one that I have long struggled with.
It's a rhythm that's &lt;em&gt;very&lt;/em&gt; common to have trouble with.
But this time, unlike syncopation?
This time, I knew I could do it.
I just had to put in the time.&lt;/p&gt;
&lt;p&gt;Triplets took me longer to get than syncopation.
The glimmers were there, and in our first session working on triplets he got me to produce a 3:2 polyrhythm &lt;em&gt;once&lt;/em&gt;!
Oh, that feeling.
I chased that feeling for the following week.
But I wasn't getting it, I was really struggling.
No matter what I did, I couldn't figure it out.&lt;/p&gt;
&lt;p&gt;I'd think about where the beat is, what the subdivisions are, and I'd still keep missing it.
By the time I had thought the subdivision, it was gone, and I was late.
Or I'd anticipate it coming, and I was early.
I can analyze the rhythm, but playing it was beyond me.&lt;/p&gt;
&lt;p&gt;The next week, Charles talked to me about feeling the beat in my head.
Some people do this with counting in their head: &lt;em&gt;one two three four&lt;/em&gt;.
They subdivide eights with &lt;em&gt;one and two and three and four and&lt;/em&gt;, and triplets with something like &lt;em&gt;one and a two and a&lt;/em&gt; or &lt;em&gt;one trip let two trip let&lt;/em&gt;.
Other people feel a pulse for the beat.&lt;/p&gt;
&lt;p&gt;I've tried to count in my head, and I can do it when I'm listening to music.
But I can't do it when I'm playing; the act of counting as words seems to force out everything else.
A pulse, though... that I feel.
The pulse I feel lives a few inches above and behind my head.
I can't describe what it feels like except that I know it's there, and it's somehow located in space, but it's not a sound.
The closest I can come is that it's like something is squeezing on each beat and subdivision, except it's squeezing something that's not inside me.&lt;/p&gt;
&lt;p&gt;This doesn't happen to me every time, and if I think about the pulse that's there, it can go away.
If I try to peek at it, try to force everything onto it, then it's gone.
I was trying to do an exercise and Charles stopped me, and then started telling me a story that was related.
But while he did that, the metronome was still going.
The pulse in my head continued, got stronger.&lt;/p&gt;
&lt;p&gt;I interrupted him to do the exercise.
I nailed it.&lt;/p&gt;
&lt;p&gt;"What changed?" he asked, with a smile on his face.&lt;/p&gt;
&lt;p&gt;I told him that I stopped thinking about the exercise.
Instead, I let myself just &lt;em&gt;feel&lt;/em&gt; the pulse, and &lt;em&gt;feel&lt;/em&gt; where I needed to be.
And it worked.&lt;/p&gt;
&lt;p&gt;Since that lesson, I've kept working on this and have gotten better at summoning the pulse, at feeling instead of thinking.
Don't get me wrong: I need to think about the rhythms to understand them before I can play them.
And I certainly need to think about the mistakes I've made so I can understand them to fix them the next time I do that.
But the core is to &lt;em&gt;feel&lt;/em&gt; when I do things, instead of analyzing.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;This turns out to be much broader than music and creative writing for me.
It is an approach that is more fundamental than that.&lt;/p&gt;
&lt;p&gt;This blog post itself is an example of that.
When I started writing it, I knew mostly that it would talk about triplets and the pulse and feeling and thinking, but I didn't know what the story was.
I didn't have an outline.
But I let myself feel where it starts, where it takes twists and turns, and where it ends up.&lt;/p&gt;
&lt;p&gt;This comes up in how I understand myself.
Instead of purely analyzing things, I can sit with the feelings and... feel them.
It's always hard, and sometimes it's scary.
And it never feels like something is happening, but yet, later, I have a deeper understanding of things.
I don't see the feelings, but feeling them brings some clarity.
Sometimes.&lt;/p&gt;
&lt;p&gt;And it even comes up in software design decisions and in writing code.
You have to vibe with it sometimes and go with feelings.
We have standard words for this, like "code smell," which are very much related to feelings but couched in a less feelings-sounding veneer.&lt;/p&gt;
&lt;p&gt;I layer in both approaches together.
I need to think about problems in order to load them into my head and solve them.
But I also need to feel problems in order to solve them &lt;em&gt;well&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Thinking can be great for figuring out what we want to do.
But actually doing it?
That's when it's important to feel, not think.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Thank you to &lt;a href="https://erikarow.land/"&gt;Erika&lt;/a&gt; and &lt;a href="https://alanza.xyz/"&gt;Robbie&lt;/a&gt; for feedback on a draft of this post.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 23 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/feel-dont-think/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>ABC: Learning The Alphabet with Java Annotations</title><link>https://serce.me/posts/2024-12-23-abc-learning-the-alphabet-with-java-annotations</link><description>This Christmas, let's explore the alphabet of Java annotations. There are so many, but how many of them truly benefit us?</description><author>SerCe's blog</author><pubDate>Mon, 23 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://serce.me/posts/2024-12-23-abc-learning-the-alphabet-with-java-annotations</guid></item><item><title>Interesting Books I’ve Read in 2024</title><link>http://bcmullins.github.io/interesting-books-2024/</link><description>Below are some interesting books I’ve read in 2024. The bulk of what I’ve been reading is either wrapped up in my research on differential privacy or for my project looking back at research from 100, 150, and 200 years ago. Two of the four recommendations below are from the latter project, but all four are of interest from an historical perspective. Wisdom’s Workshop is a history of the American research university from Medieval times to the present. William Stanley Jevons’ The Principles of Science is a wide-ranging treatment of logic and philosophy of science from 1874 that’s bursting with ideas - some more developed than others. Ballyhoo! is a history of professional wrestling and combat sports from its outlaw roots in the late nineteenth century through the first half of the twentieth century. Finally, John Ramsay McCulloch’s Discourse on Political Economy from 1824 is the first history of economic thought from the era of the classical economists.</description><author>Brett Mullins</author><pubDate>Mon, 23 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://bcmullins.github.io/interesting-books-2024/</guid></item><item><title>Unraveling the Internals of Video Streaming services</title><link>https://engineeringatscale.substack.com/p/unraveling-the-internals-of-video</link><description>A comprehensive guide to key concepts, terminologies and working of Video Streaming apps</description><author>Engineering At Scale</author><pubDate>Sun, 22 Dec 2024 14:35:54 GMT</pubDate><guid isPermaLink="true">https://engineeringatscale.substack.com/p/unraveling-the-internals-of-video</guid></item><item><title>Some TILs</title><link>https://olshansky.info/posts/some-tils/</link><description>&lt;ol&gt;
&lt;li&gt;Ulysses pact: a freely made decision to bind yourself in the future. Used for living wills in modern day but originated from Greek mythology.&lt;/li&gt;
&lt;/ol&gt;</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 22 Dec 2024 14:21:16 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/some-tils/</guid></item><item><title>On Writing: A Retrospective</title><link>https://www.xorvoid.com/on_writing.html</link><description>On Writing: A Retrospective</description><author>xorvoid</author><pubDate>Sun, 22 Dec 2024 13:51:33 GMT</pubDate><guid isPermaLink="true">https://www.xorvoid.com/on_writing.html</guid></item><item><title>Summary: Clear Thinking by Shane Parrish</title><link>https://www.chestergrant.com/summary-clear-thinking-by-shane-parrish</link><description>&lt;div class="posthaven-post-body"&gt;&lt;p&gt;&lt;/p&gt;&lt;center&gt;        &lt;div class="posthaven-gallery" id="posthaven_gallery[2184684]"&gt;
                  &lt;p class="posthaven-file posthaven-file-image posthaven-file-state-processed"&gt;
          &lt;img class="posthaven-gallery-image" src="https://phaven-prod.s3.amazonaws.com/files/image_part/asset/3263499/K41SZ0Iq5e8_e2q8pGg-9FhaSWU/medium_cover.png" /&gt;
        &lt;/p&gt;

        &lt;/div&gt;
&lt;/center&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;1. While the rest of us are chasing victory, the best in the world know they must avoid losing before they can win. It turns out this is a surprisingly effective strategy.&lt;/p&gt;&lt;p&gt;2. If there is a tagline to my life, it is “Mastering the best of what other people have already figured out,” and this book is a tribute to that belief.&lt;/p&gt;&lt;p&gt;3. Time is the friend of someone who is properly positioned and the enemy of someone poorly positioned. When you are well positioned, there are many paths to victory. If you are poorly positioned, there may be only one. You can think of this a bit like playing Tetris. When you play well, you have many options for where to put the next piece. When you play poorly, you need just the right piece.&lt;/p&gt;&lt;p&gt;&lt;b&gt;4. It doesn’t matter what position you find yourself in right now. What matters is whether you improve your position today. Every ordinary moment is an opportunity to make the future easier or harder. It all depends on whether you’re thinking clearly.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;5. Never forget that your unconscious is smarter than you, faster than you, and more powerful than you. It may even control you. You will never know all of its secrets.&lt;/p&gt;&lt;p&gt;6. In the space between stimulus and response, one of two things can happen. You can consciously pause and apply reason to the situation. Or you can cede control and execute a default behavior.&lt;/p&gt;&lt;p&gt;7. So our first step in improving our outcomes is to train ourselves to identify the moments when judgment is called for in the first place, and pause to create space to think clearly.&lt;/p&gt;&lt;p&gt;&lt;b&gt;8. There’s nothing stronger than biological instincts. They control us often without us even knowing. Failing to come to terms with them only makes you more susceptible to their influence.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;9. Here’s how each essentially functions: The emotion default: we tend to respond to feelings rather than reasons and facts. The ego default: we tend to react to anything that threatens our sense of self- worth or our position in a group hierarchy. The social default: we tend to conform to the norms of our larger social group. The inertia default: we’re habit forming and comfort seeking. We tend to resist change, and to prefer ideas, processes, and environments that are familiar.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;10. The social rewards for going with the crowd are felt long before the benefits of going against it are gained. One measure of a person is the degree to which they’ll do the right thing when it goes against the popular belief. However, it is easy to overestimate our willingness to diverge from the crowd, and underestimate our biological instinct to fit in.&lt;/p&gt;&lt;p&gt;11. Princeton professor Robert George wrote, “I sometimes ask students what their position on slavery would have been had they been white and living in the South before abolition. Guess what? They all would have been abolitionists! They all would have bravely spoken out against slavery, and worked tirelessly against it.” 1 No, they wouldn’t have. They may understandably want to send that signal now when it’s safe to do so, but back then they would have likely behaved the same most everyone else did at the time. &lt;/p&gt;&lt;p&gt;12. The only way to outperform if you’re doing undifferentiated work is to work harder than everyone else. &lt;/p&gt;&lt;p&gt;13. Imagine a team of ditchdiggers working with their hands. A slight variation in the amount of soil moved per hour is barely perceptible. Your work is indistinguishable from that of the person next to you. The only way to move more dirt is to dig for longer. &lt;/p&gt;&lt;p&gt;14. Within this paradigm, the ditchdigger who takes a week off to experiment and invent the shovel seems crazy. Not only do they look like a fool for taking a risk, but their cumulative production falls behind for every day they are not digging. Only when the shovel comes along do others see its advantage. Success requires shamelessness. So too does failure.&lt;/p&gt;&lt;p&gt;15. Doing something different means you might underperform, but it also means you might change the game entirely. If you do what everyone else does, you’ll get the same results that everyone else gets. Best practices aren’t always the best. By definition, they’re average.&lt;/p&gt;&lt;p&gt;&lt;b&gt;16. Lou Brock might have put it best when he said, “Show me a guy who’s afraid to look bad, and I’ll show you a guy you can beat every time.” In other words, someone who’s possessed by the social default is easy to defeat.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;17. The inertia default pushes us to maintain the status quo. Starting something is hard but so too is stopping something. We resist change even when change is for the best.&lt;/p&gt;&lt;p&gt;18. “Once our minds are set in a direction, they tend to continue in that direction unless acted upon by some outside force.”&lt;/p&gt;&lt;p&gt;19. The good news is that the same biological tendencies that make us react without reasoning can be reprogrammed into forces for good.&lt;/p&gt;&lt;p&gt;&lt;b&gt;20. The more time you spend with people, the more likely you start to think and act as they do.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;21. The way to improve your defaults isn’t by willpower but by creating an intentional environment where your desired behavior becomes the default behavior.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;22. Joining groups whose default behaviors are your desired behavior is an effective way to create an intentional environment. &lt;/b&gt;&lt;/p&gt;&lt;p&gt;23. Our defaults work off deeply ingrained biological tendencies— our tendencies for self- preservation, for recognizing and maintaining social hierarchies, and for defending ourselves and our territory. We can’t simply know these tendencies exist and then will them out of existence. On the contrary, the feeling that willpower is all it takes to remove these forces is one of the tricks they use to keep us under their control.&lt;/p&gt;&lt;p&gt;24. To stop our defaults from impeding good judgment, we need to harness equally powerful biological forces. We need to take the same forces that the defaults would use to ruin us and turn them to our advantage. Chief among them is the force of inertia.&lt;/p&gt;&lt;p&gt;25. Establishing rituals is the key to creating positive inertia. Rituals focus the mind on something other than the moment. They can be as simple as taking a quick pause before responding to someone’s point of contention at work. One of my old mentors used to tell me, “When someone slights you in a meeting, take a deep breath before you speak and watch how often you change what you’re about to say.”&lt;/p&gt;&lt;p&gt;26. One effective question to ask yourself before you act is, “Will this action make the future easier or harder?”&lt;/p&gt;&lt;p&gt;27. My grandfather (and many others) used to say, “If you find yourself in a hole, the first thing you need to do is stop digging.”&lt;/p&gt;&lt;p&gt;29. No successful person wants to work with a chronic victim. The only people who want to work with victims are other victims.&lt;/p&gt;&lt;p&gt;30. Know thyself.—INSCRIPTION ON THE TEMPLE OF APOLLO AT DELPHI&lt;/p&gt;&lt;p&gt;31. Emotional intensity is far less important in the long run than disciplined consistency. Inspiration and excitement might get you going, but persistence and routine are what keep you going until you reach your goals.&lt;/p&gt;&lt;p&gt;&lt;b&gt;32. Confidence also Comes from How You Talk to Yourself More dreams die from a lack of confidence than a lack of competence. But while confidence is often a byproduct of our accomplishments, it also comes from how you talk to yourself.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;33. Self- confidence is also the strength to accept hard truths. We all have to deal with the world as it is, not as we want it to be. The quicker you stop denying inconvenient truths and start responding to difficult realities, the better.&lt;/p&gt;&lt;p&gt;34. SELF- ACCOUNTABILITY, SELF- KNOWLEDGE, SELF- CONTROL, and self- confidence are essential to exercising good judgment. Here are a couple examples of how they work together.&lt;/p&gt;&lt;p&gt;35. Suppose you know from experience that you’re susceptible to social pressure. To protect yourself from the influence of the social default, you decide to implement a safeguard. You form a rule for yourself: never say yes to something important without thinking it over for a day.&lt;/p&gt;&lt;p&gt;36. Practicing this safeguard isn’t very enjoyable. Putting someone on hold for a day might be uncomfortable.&lt;/p&gt;&lt;p&gt;37. As simple as they seem, automatic rules for common situations get results. We’ll explore automatic rules in the next chapter.&lt;/p&gt;&lt;p&gt;38. It is inevitable if you enter into relations with people on a regular basis… that you will grow to be like them…. Place an extinguished piece of coal next to a live one, and either it will cause the other one to die out, or the live one will make the other reignite…. Remember that if you consort with someone covered in dirt you can hardly avoid getting a little grimy yourself.—EPICTETUS, Discourses&lt;/p&gt;&lt;p&gt;&lt;b&gt;39. Few things are more important in life than avoiding the wrong people. It’s tempting to think that we are strong enough to avoid adopting the worst of others, but that’s not how it typically works.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;40. We unconsciously become what we’re near. If you work for a jerk, sooner or later you’ll become one yourself. If your colleagues are selfish, sooner or later you become selfish. If you hang around someone who’s unkind, you’ll slowly become unkind. Little by little, you adopt the thoughts and feelings, the attitudes and standards of the people around you.&lt;/p&gt;&lt;p&gt;41. Few people realize that exceptional outcomes are almost always achieved by people with higher- than- average standards.&lt;/p&gt;&lt;p&gt;&lt;b&gt;42. Champions don’t create the standards of excellence. The standards of excellence create champions. &lt;/b&gt;&lt;/p&gt;&lt;p&gt;43. The best teachers expect more from their students and from themselves. And more often than not, the students rise to meet those expectations. The best leaders expect more from people; they hold them to the same standards they hold themselves— a higher standard than most would otherwise know is possible.&lt;/p&gt;&lt;p&gt;44. When we accept substandard work from others, it’s for the same reason: we’re not all in. When you’re committed to excellence, you don’t let anyone on your team half- ass it. You set the bar, you set it high, and you expect anyone working with you to work just as hard and level up to what you expect or above. Anything less is unacceptable.&lt;/p&gt;&lt;p&gt;45. Masters of their craft don’t merely want to check off a box and move on. They’re dedicated to what they do, and they keep at it. Master- level work requires near fanatical standards, so masters show us what our standards should be.&lt;/p&gt;&lt;p&gt;46. Working with a master firsthand is the best education; it’s the surest way of raising the bar. Their excellence demands your excellence. But most of us aren’t lucky enough to have that opportunity.&lt;/p&gt;&lt;p&gt;47. THERE ARE TWO COMPONENTS TO BUILDING STRENGTH by raising the bar: (a) Choose the right exemplars— ones that raise your standards. Exemplars can be people you work with, people you admire, or even people who lived long ago. It doesn’t matter. What matters is they make you better in a certain area, like a skill, trait, or value.&lt;/p&gt;&lt;p&gt;&lt;b&gt;48. In the previous section, we discussed something most people never think about: if you don’t curate the people in your life, the people who end up surrounding you will be there by chance and not by choice. That group includes your parents, your friends, your family, your coworkers. Sure, your high school friends might be great examples of character and acumen, but odds are they’re average. Sure, your parents might be some of the smartest businesspeople in the world, but odds are they’re not. It’s not that you should remove these people from your life, though; controlling your environment just means intentionally adding exemplars into the mix.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;49. As Peter Kaufman once told me, “No technique has been more responsible for my success in life than studying and adopting the good models of others.”&lt;/p&gt;&lt;p&gt;50. One of the biggest mistakes that I see people make is they don’t want to learn from someone who has a character blemish or a worldview that doesn’t align with theirs.&lt;/p&gt;&lt;p&gt;51.  “I shall never be ashamed of citing a bad author if the line is good.” Or, as Cato the Elder put it, “Be careful not to rashly refuse to learn from others.” Don’t throw away the apple because of a bruise on the skin.&lt;/p&gt;&lt;p&gt;52. The only person you’re competing with is the person you were yesterday. Victory is being a little better today. **&lt;/p&gt;&lt;p&gt;&lt;b&gt;53. Bad habits are easy to acquire when there is a delay between action and consequence. If you eat a chocolate bar or skip a workout today, you’re not going to suddenly go from healthy to unhealthy. Work late and miss dinner with your family a couple nights, and it won’t damage your relationship. If you spend today on social media instead of doing work, you’re not going to get fired. However, these choices can end up becoming habits through repetition and accumulate into disaster.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;54. The formula for failure is a few small errors consistently repeated.&lt;/p&gt;&lt;p&gt;55. There are two ways to manage your weaknesses. The first is to build your strengths, which will help you overcome the weaknesses you’ve acquired. The second is to implement safeguards, which will help you manage any weaknesses you’re having trouble overcoming with strength alone.&lt;/p&gt;&lt;p&gt;56. THERE ARE MANY INBUILT BIOLOGICAL VULNERABILITIES that can impede good judgment: sleep deprivation, hunger, fatigue, emotion, distraction, stress from feeling rushed, and being in an unfamiliar environment are just some examples. We can’t avoid finding ourselves in these conditions from time to time. But we can implement safeguards to protect us from our defaults when we are. &lt;/p&gt;&lt;p&gt;57. Safeguards are tools for protecting ourselves from ourselves— from weaknesses that we don’t have the strength to overcome.&lt;/p&gt;&lt;p&gt;58. Purging your home of all junk food is an example of one safeguarding strategy: increasing the amount of “friction” required to do something that’s contrary to your long- term goals.&lt;/p&gt;&lt;p&gt;59. My favorites include prevention, creating rules for yourself, making checklists, shifting your frame of reference, and making the invisible visible. Let’s talk about each strategy.&lt;/p&gt;&lt;p&gt;60. The first kind of safeguard aims at preventing problems before they happen. One way to do this is to avoid decision- making in unfavorable conditions. Stress, for instance, is a big contributor to bad decisions. Some studies have shown that stress short- circuits the deliberation process— it undermines the systematic evaluation of alternatives that’s needed for effective decision- making.&lt;/p&gt;&lt;p&gt;&lt;b&gt;61. Alcoholics Anonymous has a helpful safeguard for its members. They call it HALT— an acronym that stands for Hungry, Angry, Lonely, and Tired. When you feel like having a drink, they say, ask yourself whether any of these conditions apply. If so, deal with the real problem— hunger, anger, loneliness, or fatigue— instead of reaching for a drink.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;62. Daniel Kahneman, the godfather of cognitive biases and thinking errors, he revealed an unexpected way we can improve our judgment: replacing decisions with rules.&lt;/p&gt;&lt;p&gt;63. When it comes to your health, just like many other elements of your life, environment determines behavior. Your environment makes one path easier than another. &lt;/p&gt;&lt;p&gt;64. It’s easier to make healthy food choices if the only foods available to you are good for you. It’s also easier to stick to a consistent pattern of choices if you’re in your familiar operating environment. When you’re in an unfamiliar environment, it’s harder to maintain your familiar patterns of behavior, which is why a lot of people stop exercising or eating healthy when they travel.&lt;/p&gt;&lt;p&gt;65. Your environment isn’t just your physical surroundings. It also includes people. Sometimes it’s hard saying no to someone. &lt;/p&gt;&lt;p&gt;66. We’re wired in a way that makes us want to be liked by others, and we’re afraid that saying no to someone will make them like us less. Saying no to someone repeatedly can be even more difficult. We might say no when our friend offers us a sugary beverage after a workout one day, but if he does it three days in a row, we cave. That’s only human.&lt;/p&gt;&lt;p&gt;67. In a quirk of psychology, people typically don’t argue with your personal rules. They just accept them as features of who you are. People question decisions, but they respect rules.&lt;/p&gt;&lt;p&gt;68. Kahneman told me his favorite rule was never to say yes to a request on the phone. He knows that he wants people to like him, so he wants to say yes in the moment, but after filling up his schedule with things that didn’t make him happy, he decided to be more vigilant about what he agrees to do and why. When people ask him for things over the phone now, he says something along the lines of, “I’ll have to get back to you after I think about it.” Not only does this give him time to think without the immediate social pressure, but it also allows a lot of these requests to just drop away because people choose not to follow up. He rarely gets back to any of these people and says yes.&lt;/p&gt;&lt;p&gt;69. Another safeguarding strategy is to increase the amount of effort it takes to do things that are contrary to your goals. I used to find myself checking my email whenever I have a second. I’d check it before I got out of bed, on the walk home from work, in line at the grocery store.**&lt;/p&gt;&lt;p&gt;&lt;b&gt;70. If there were a recipe for accumulated disaster, it would be giving the best of ourselves to the least important things and the worst of ourselves to the most important things. &lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;71. Checklists, for instance, offer a simple way to override your defaults. Pilots go through a preflight checklist every time they fly. Surgeons go through preoperative checklists every time they operate. You might have a packing checklist every time you travel. In each of these cases, the checklist acts as a safeguard, forcing us to slow down whatever we’re doing and go back to basics. &lt;/b&gt;&lt;/p&gt;&lt;p&gt;72. ONCE YOU HAVE reprogrammed your defaults to create space for clear thinking, you must master the skill of decision- making.&lt;/p&gt;&lt;p&gt;73. Decisions are different from choices. If you casually select an option from a range of alternatives, you’ve made a choice. If you react without thinking, you’ve made an unconscious choice. But neither of these is the same as a decision. A decision is a choice that involves conscious thought.&lt;/p&gt;&lt;p&gt;74. That process is about weighing your options with the aim of selecting the best one, and it’s composed of four stages: defining the problem, exploring possible solutions, evaluating the options, and finally making the judgment and executing the best option. We will discuss each of these components in detail throughout this chapter.&lt;/p&gt;&lt;p&gt;75. When the stakes are low, inaction hurts you more than speed. Sometimes it’s better just to make a quick choice and not spend time deliberating&lt;/p&gt;&lt;p&gt;76. Why waste time when action is inconsequential and its effects are easily reversed? For example, if there are two identical squat racks in the gym and both are momentarily open, it makes no difference which one you take. If you wait and decide, they’ll both be taken by someone else. Just choose either one.&lt;/p&gt;&lt;p&gt;77. When the stakes are higher, though, speed can hurt you. If an action could have a major impact on your life or your business and its effects can’t be reversed, you must decide and not merely choose.&lt;/p&gt;&lt;p&gt;78. In these cases, the magnitude of the potential losses makes careful decision- making a worthwhile investment of your time. In these cases, evaluate the options and decide. Don’t just choose. &lt;/p&gt;&lt;p&gt;79. The next few sections describe some tools for reasoning better when making decisions. They won’t solve every decision- making problem, because no tool is right for every job; each has its uses and limitations. You need multiple tools in your toolbox. Otherwise, you end up solving the wrong problems. As the old adage says, “If the only tool you have is a hammer, you tend to see every problem as a nail.”&lt;/p&gt;&lt;p&gt;80. “What would have to be true for this problem not to exist in the first place?” The way you define a problem changes what you see.&lt;/p&gt;&lt;p&gt;81. A mentor of mine once taught me that the best way to avoid finding the perfect solution to the wrong problem at work, when time allows, is to hold two separate meetings: one to define the problem, and one to come up with the solution.&lt;/p&gt;&lt;p&gt;82. One way to keep meetings short and avoid the signaling that comes from repeating information that everyone knows is simply asking everyone, “What do you know about this problem that other people in the room don’t know?”&lt;/p&gt;&lt;p&gt;83. Remember that writing out the problem makes the invisible visible. Write down what you think the problem is, and then look at it the next day. If you find yourself using jargon in your description, it’s a sign that you don’t fully understand the problem. And if you don’t understand it, you shouldn’t be making a decision about it.&lt;/p&gt;&lt;p&gt;84. SAFEGUARD: Use the test of time. Test whether you’re addressing the root cause of a problem, rather than merely treating a symptom, by asking yourself whether it will stand the test of time. Will this solution fix the problem permanently, or will the problem return in the future? If it seems like the latter, then chances are you’re only treating a symptom.&lt;/p&gt;&lt;p&gt;85. You can put your energy into short- term solutions or long- term solutions but not both. Any energy that’s channeled toward short- term solutions depletes energy that could be put into finding a long- term fix.&lt;/p&gt;&lt;p&gt;86. ONCE YOU’RE CLEAR ON THE PROBLEM, IT’S TIME TO THINK of possible solutions— ways of overcoming the obstacles to get what you want. The way to come up with possible solutions is by imagining different possible futures— different ways the world could turn out.&lt;/p&gt;&lt;p&gt;87. Luckily, there’s a way to convert the hindsight of tomorrow into the foresight of today. It’s a thought experiment that psychologists call premortem. The concept isn’t new, it originates in Stoic philosophy. Seneca used premeditatio malorum (“ the premeditation of evils”) to prepare for the inevitable ups and downs of life. The point isn’t to worry about problems; it’s to fortify and prepare for them.&lt;/p&gt;&lt;p&gt;88. Imagining what could go wrong doesn’t make you pessimistic.&lt;/p&gt;&lt;p&gt;89. “Failure comes from a failure to imagine failure.” &lt;/p&gt;&lt;p&gt;90. THE 3 + PRINCIPLE: Force yourself to explore at least three possible solutions to a problem. If you find yourself considering only two options, force yourself to find at least one more.&lt;/p&gt;&lt;p&gt;91. SAFEGUARD: Imagine that one of the options is off the table. Take each of the options you’re considering, and one at a time, ask yourself, “What would I do if that were not possible?”&lt;/p&gt;&lt;p&gt;92. SAFEGUARD: Come up with Both- And options. Try to find ways of combining the binary. Think not in terms of choosing either X or Y, but rather having both X and Y.&lt;/p&gt;&lt;p&gt;93. This is an important aspect of leadership and problem-solving in general: you have to pick one criterion above all.&lt;/p&gt;&lt;p&gt;94. Most information is irrelevant. irrelevant. Knowing what to ignore—separating the signal from the noise—is the key to not wasting valuable time. Think, for example, of investment decisions. The best investors know which variables probabilistically govern the outcomes, and they pay attention to those. They don’t ignore everything else, but focusing primarily on those variables allows them to filter massive amounts of information very quickly.&lt;/p&gt;&lt;p&gt;100. The quality of your decisions is directly related to the quality of your thoughts. The quality of your thoughts is directly related to the quality of your information. Many people treat all sources of information as if they’re equally valid. They’re not. While you might value getting everyone’s opinion, that doesn’t mean each opinion should be equally weighted or considered.**&lt;/p&gt;&lt;p&gt;101. SAFEGUARD: Run an experiment. Try something out to see what kinds of results it yields.&lt;/p&gt;&lt;p&gt;&lt;b&gt;102. An experiment is a low-risk way of gathering important information. For example, if you want to know whether people will pay for something, try to sell it before you even create it. &lt;/b&gt;&lt;/p&gt;&lt;p&gt;103. That’s what my friends at Tuft &amp;amp; Needle did. They were one of the first companies to ship foam mattresses directly to consumers’ homes. They shared an incredible story with me over coffee one day, about their early days. In order to validate their idea, they set up a landing page, bought some Facebook ads, and started taking orders. &lt;/p&gt;&lt;p&gt;104. They didn’t even have a product or a company yet; they just wanted to see if people would buy foam mattresses from them. After a few days of receiving orders, they had all the proof they needed that people would buy their product. They refunded all the orders and officially started their company. While this example may be a bit unorthodox, there are many ways in which experimenting can help determine whether there’s sufficient demand for a product or service.&lt;/p&gt;&lt;p&gt;105. Experts can increase the accuracy of your information and decrease the time it takes to get it. Getting even one expert’s advice can cut through a lot of confusion and help you quickly formulate and/or eliminate options.&lt;/p&gt;&lt;p&gt;106. Experts don’t treat all requests for help equally, though. Some requests really don’t feel good to receive. Usually these are requests of the tell-me-what-I-should-do type. Often these people haven’t done the work ahead of time, they just want you to decide for them.&lt;/p&gt;&lt;p&gt;107. Remember: the goal isn’t to have someone tell you what to do; rather, it’s to learn how an expert thinks about the problem, which variables they consider relevant, and how those variables interact over time.**&lt;/p&gt;&lt;p&gt;108. THE STOP, FLOP, KNOW PRINCIPLE: Stop gathering more information and execute your decision when either you Stop gathering useful information, you First Lose an OPportunity (FLOP), or you come to Know something that makes it evident what option you should choose.&lt;/p&gt;&lt;p&gt;109. When failure is expensive, it’s worth investing in large margins of safety. A margin of safety is a buffer between what you expect to happen and what could happen. It’s designed to save you when surprises are expensive.&lt;/p&gt;&lt;p&gt;110. A margin of safety is like having insurance. If you know in advance you won’t need to make a claim this year, it’s a waste of money to buy insurance. The problem is that you don’t know in what year you’ll need to make a claim, so you buy it every year. It might seem like a waste of money in years when nothing happens, but it shows its real value in years when something does.&lt;/p&gt;&lt;p&gt;111. Building a margin of safety means giving yourself as much cushioning and coverage in the future as possible. It’s a way of preparing yourself for the widest range of possible future outcomes—and protecting yourself against the worst ones.&lt;/p&gt;&lt;p&gt;112. TIP: The margin of safety is often sufficient when it can absorb double the worst-case scenario. So the baseline for a margin of safety is one that could withstand twice the amount of problems that would cause a crisis, or maintain twice the amount of resources needed to rebuild after a crisis.&lt;/p&gt;&lt;p&gt;113. For example, if you want to feel financially secure even if you lose your job, you can estimate how long it will take you to gain employment again, and then save enough to live off savings for double that amount of time.&lt;/p&gt;&lt;p&gt;114. However, if you have a lot of expertise and data, you can reduce your margin of safety yet further. Here’s an example: Warren Buffett aims to buy stocks that are 30–50 percent less than their true value. So he has a 30–50 percent margin of safety on stocks. But he’ll pay close to a dollar on the dollar for stocks that he understands well. So there’s only maybe a 20 percent margin of safety on the stocks he’s the most confident in.&lt;/p&gt;&lt;p&gt;115. One of Warren Buffett’s core tenets for buying a business is that if he doesn’t understand it, he doesn’t buy it. In other words, if he doesn’t have enough information to calculate a margin of safety, he doesn’t invest at all.&lt;/p&gt;&lt;p&gt;&lt;b&gt;116. Remembering that I’ll be dead soon is the most important tool I’ve ever encountered to help me make the big choices in life. Because almost everything—all external expectations, all pride, all fear of embarrassment or failure—these things just fall away in the face of death, leaving only what is truly important. Remembering that you are going to die is the best way I know to avoid the trap of thinking that you have something to lose.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;117. At some point my kids figured out that it was easier to solve a maze backward than forward, especially if the maze is harder or more complicated than usual. Something about starting with the end in mind, they realized, makes it easier to decide which path to take. Life in general works similarly.&lt;/b&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Chester Grant</author><pubDate>Sun, 22 Dec 2024 13:26:15 GMT</pubDate><guid isPermaLink="true">https://www.chestergrant.com/summary-clear-thinking-by-shane-parrish</guid></item><item><title>Ten Favorite Photos I Captured in 2024</title><link>https://saeedesmaili.com/posts/ten-favorite-photos-i-captured-in-2024/</link><description>&lt;p&gt;I purchased a Sony a7ii in 2023, while having zero understanding of the basics of cameras and photography. I recall being particularly confused about the &lt;a href="https://petapixel.com/exposure-triangle/" target="_blank"&gt;exposure triangle&lt;/a&gt;
, especially how aperture affects the sharpness and the depth of field of an image. After learning the basics and taking around 3500 photos with that camera, I realized its aging technology was limiting my beginner skills. I had enough experience to be able to research camera models and their features, and I decided to get a Sony a7cii at the beginning of 2024, a decision I&amp;rsquo;m still happy with today.&lt;/p&gt;</description><author>Saeed Esmaili</author><pubDate>Sun, 22 Dec 2024 12:02:38 GMT</pubDate><guid isPermaLink="true">https://saeedesmaili.com/posts/ten-favorite-photos-i-captured-in-2024/</guid></item><item><title>Detecting the Hammer Candlestick Pattern Using Python</title><link>https://blog.adnansiddiqi.me/detecting-the-hammer-candlestick-pattern-using-python/</link><description>&lt;p&gt;This post is part of the T4p Series. In the previous post, we discussed creating your custom signal generation pattern. In this post, we will discuss a famous candlestick pattern called the Hammer pattern. We will discuss the pattern, what it is all about, and how it helps traders to earn money. Introduction A Hammer is a single candlestick pattern that forms during a downtrend and is characterized by having a small real body at the top of the candle, a long lower shadow (at least 2-3 times the size of the body), and little to no upper shadow. The pattern indicates a potential trend reversal as it shows that despite strong selling pressure (long lower shadow), buyers ultimately regained control by pushing prices back up (small body at the top). As you can see in the image below, it is the 4th one from the left in green. Types of Hammer Candlestick There are three two types of hammer patterns: Regular Hammer (appears after a downtrend). Inverted Hammer (appears after an uptrend). Usage Below are the usages of the hammer pattern: Trend Reversal Signal: Indication about the potential reversal from downtrend to uptrend(bullish) or vice versa. Support and Resistance: Highlights key support levels in downtrends and resistance in uptrends. Confirmation: Best used with other indicators for confirming trend reversals. Spotting The Hammer Pattern The close price is near the high of the candle. The lower shadow is at least twice as long as the body. The upper shadow is minimal or absent. Development Visualization Like before, the first thing we will do is write code for visualizing the pattern itself. Assuming all the required libraries are installed (discussed multiple times in previous posts), I&amp;#8217;m going to use ChatGPT to generate fictitious OHLC data tailored to my requirements. def candle_hammer(df): # Fill NaN values with 0 df = df.fillna(0) return ( ((df["High"] - df["Low"]) &amp;#62; 3 * (df["Open"] - df["Close"])) &amp;#38; (((df["Close"] - df["Low"]) / (0.001 + df["High"] - df["Low"])) &amp;#62; 0.6) &amp;#38; (((df["Open"] - df["Low"]) / (0.001 + df["High"] - df["Low"])) &amp;#62; 0.6) ) data = { 'Date': pd.date_range('2024-12-01', periods=10, freq='D'), 'Open': [100, 105, 110, 115, 108, 112, 116, 118, 122, 125], 'High': [105, 110, 115, 118, 113, 116, 119, 121, 124, 128], 'Low': [98, 103, 107, 112, 106, 110, 113, 116, 119, 120], 'Close': [103, 107, 109, 115, 110, 114, 118, 119, 123, 126] } # # Adjust the values of the 3rd candle (index 2) to create a Bullish Hammer pattern # Modify the data to make only the 3rd candle (index 2) a Bullish Hammer data['Open'][2] = 108 # Small body for Bullish Hammer data['Close'][2] = 109 # Small body for Bullish Hammer data['Low'][2] = 100 # Long lower shadow data['High'][2] = 110 # Small upper shadow # Modify the other candles to have larger bodies data['Open'][3] = 115 # Bullish candle with higher close data['Close'][3] = 120 # Higher close than open data['Low'][3] = 110 # Normal low data['High'][3] = 125 # Normal high data['Open'][4] = 120 # Bearish candle with lower close data['Close'][4] = 118 # Lower close than open data['Low'][4] = 115 # Normal low data['High'][4] = 125 # Normal high data['Open'][5] = 125 # Bearish candle with lower close data['Close'][5] = 121 # Lower close than open data['Low'][5] = 120 # Normal low data['High'][5] = 130 # Normal high data['Open'][6] = 130 # Bullish candle with higher close data['Close'][6] = 134 # Higher close than open data['Low'][6] = 125 # Normal low data['High'][6] = 135 # Normal high data['Open'][7] = 135 # Bullish candle with higher close data['Close'][7] = 137 # Higher close than open data['Low'][7] = 130 # Normal low data['High'][7] = 140 # Normal high data['Open'][8] = 140 # Bullish candle with higher close data['Close'][8] = 141 # Higher close than open data['Low'][8] = 135 # Normal low data['High'][8] = 145 # Normal high data['Open'][9] = 145 # Bullish candle with higher close data['Close'][9] = 146 # Higher close than open data['Low'][9] = 140 # Normal low data['High'][9] = 150 # Normal high # Convert to DataFrame df = pd.DataFrame(data) df.set_index('Date', inplace=True) When I run the above code in the notebook, it generates the following chart: &amp;#160; I borrowed the candle_hammer function from here, which was actually an elegant implementation. Then, I asked GPT to generate fake OHLC data that includes a hammer candle as well. In the chart above, you can see an up-arrow indicating the hammer candle. As you see, it generated a hammer candle on December 3rd, having an up-arrow on top of it. Now, what if we run candle_hammer function? As you can see, it returned True on December 3rd. Sweet! Now, let&amp;#8217;s try these functions on actual data. I am using the Yahoo Finance library to pull daily data for TSLA. tesla_data = yf.download('TSLA', start='2024-01-01', end='2024-12-31', progress=False) tesla_data = tesla_data.tail(30) # Last 30 Entries only visualize_hammer(tesla_data,"TSLA Daily Data") candle_hammer(tesla_data) And&amp;#8230;when I run the above functions, it produces the following outputs: Sweet, isn&amp;#8217;t it? Now, if you want, you can use this data to generate signals or alerts to inform you about trend reversals Conclusion In this post, you learned how to identify trend reversals with the help of hammer patterns and how to automate this process in Python. In the next post, I will discuss another pattern, but this time with the help of a library that has already done all the work for you. As always, the code is available on GitHub Looking to create something similar or even more exciting? Schedule a meeting or email me at kadnan @ gmail.com. Love What You’re Learning Here? If my posts have sparked ideas or saved you time, consider supporting my journey of learning and sharing. Even a small contribution helps me keep this blog alive and thriving. 👉 Support My Work Here If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/detecting-the-hammer-candlestick-pattern-using-python/"&gt;Detecting the Hammer Candlestick Pattern Using Python&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Sun, 22 Dec 2024 10:48:37 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/detecting-the-hammer-candlestick-pattern-using-python/</guid></item><item><title>Expedia and SIXT: My Surprise Cancellation Fee</title><link>https://savraj.co/2024/12/22/expedia-sixt-surprise.html</link><description>I recently tried to cancel a rental car reservation on Expedia—nearly two weeks before my trip—only to discover they’d keep $100 of the $207 I’d already paid. Turns out, SIXT has a policy allowing them to pocket $100 from any cancellation, and I find it incredibly frustrating—especially given that I’ve never encountered a non-full-refund policy for rental cars so far in advance. It feels like a complete trick.</description><pubDate>Sun, 22 Dec 2024 07:00:00 GMT</pubDate><guid isPermaLink="true">https://savraj.co/2024/12/22/expedia-sixt-surprise.html</guid></item><item><title>Materials Cheat Sheet</title><link>https://taylor.town/cheat-materials</link><description>Metals, polymers, ceramics, glasses, composites, and natural stuff.</description><author>taylor.town</author><pubDate>Sun, 22 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/cheat-materials</guid></item><item><title>How to Generate and Format the Current Date and Time in Hugo with dateFormat</title><link>https://nelson.cloud/how-to-generate-and-format-the-current-date-and-time-in-hugo-with-dateformat/?ref=rss</link><description>Several examples of dateFormat including date only, date + 12-hour time, date + 24-hour time, date + timezone, and date + UTC offset.</description><author>Nelson Figueroa</author><pubDate>Sun, 22 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/how-to-generate-and-format-the-current-date-and-time-in-hugo-with-dateformat/?ref=rss</guid></item><item><title>Full RSS feed</title><link>https://paul.kinlan.me/projects/full-rss-feed/</link><description>I love RSS feeds (and ATOM too (and JSONFeed)) but one thing that frustrates me is when a feed doesn't include all of the content.
https://full-rss.deno.dev was created to partially solve this. If there is a feed that you really wish was full-content and isn't then you can replace that feed with https://full-rss.deno.dev/?url=https://developer.chrome.com/static/blog/feed.xml and it will get the 10 most recent posts, scrape the linked page and replace the content with a stripped down version of the full HTML page.</description><author>Modern Web Development with Chrome</author><pubDate>Sun, 22 Dec 2024 01:07:00 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/projects/full-rss-feed/</guid></item><item><title>Linux zhongwen shurufa</title><link>https://lmy.medium.com/linux-zhongwen-shurufa-dbfaea3496fc?source=rss-7e8adfcc60fb------2</link><description>&lt;p&gt;This article appeared on your search results page because:&lt;/p&gt;&lt;ol&gt;&lt;li&gt;you have just installed a &lt;strong&gt;desktop Linux&lt;/strong&gt;,&lt;/li&gt;&lt;li&gt;you are struggling to figure out &lt;strong&gt;how to type Chinese&lt;/strong&gt;, and&lt;/li&gt;&lt;li&gt;&lt;strong&gt;you use Pinyin&lt;/strong&gt;, not ZhuYin/Bopomofo, Cangjie, or Wubi for &lt;a href="https://en.wikipedia.org/wiki/Romanization_of_Chinese"&gt;romanized Chinese&lt;/a&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;strong&gt;What is this?&lt;/strong&gt; Unlike in Windows or macOS, where Input Methods come in a complete package, Input Methods in Linux is composed of at least two parts: &lt;em&gt;frameworks&lt;/em&gt; and &lt;em&gt;engines&lt;/em&gt;.&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;strong&gt;Frameworks&lt;/strong&gt; coordinate with the operating system for tasks like rendering “candidate words” panels and “hijacking” keyboard events from a textbox.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Engines&lt;/strong&gt; deal with mapping keystrokes to Chinese characters.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Two frameworks are most popular today: &lt;a href="https://en.m.wikipedia.org/wiki/Intelligent_Input_Bus"&gt;IBus&lt;/a&gt; and &lt;a href="https://en.m.wikipedia.org/wiki/Fcitx"&gt;Fcitx&lt;/a&gt; (小企鹅输入法, lit. “Little Penguin Input Method”). Ubuntu comes with IBus, and their stock engine of choice is &lt;a href="https://github.com/libpinyin/ibus-libpinyin"&gt;ibus-libpinyin&lt;/a&gt;. If you have tried typing Chinese in Ubuntu and liked it, then that’s the way to go. Otherwise, I commend your braveness diving straightaway into Arch.&lt;/p&gt;&lt;p&gt;I myself didn’t enjoy IBus, mainly due to how eager it was replacing Latin keystrokes with candidate words. What do I mean by that? Suppose I typed “nihao” on a keyboard. Candidate words would include “你好” and “泥嚎”. In Windows, macOS, and Linux with Fcitx, the textbox would be temporarily populated with an underlined “ni hao”, and the candidate words will show in a floating pane beneath it:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/244/1*tBqzAnpktMNPlakCGDg6ag.png" /&gt;&lt;figcaption&gt;Candidate words in macOS for typing “nihao”&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Meanwhile, IBus would pre-fill the textbox with the first candidate, rendering the Latin keystrokes inside of the floating pane:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/478/1*xd_vBPj4y77-Qmmq8YuaCA.png" /&gt;&lt;figcaption&gt;&lt;em&gt;Screenshot taken from a GIF in &lt;/em&gt;&lt;a href="https://github.com/openSUSE/Customize-IBus/blob/main/GUIDE.md"&gt;&lt;em&gt;this doc&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;Somehow this annoys me, and I’ve yet been able to find a config for this behavior. Users beware.&lt;/p&gt;&lt;p&gt;My pet peeve aside, Fcitx — particularly, fcitx5 — seems more popular (by GitHub stars). Therefore, this post will be about how to install a fcitx-based Chinese Input Method.&lt;/p&gt;&lt;p&gt;The Chinese writing system has many romanization solutions, most popular ones include Pinyin and ZhuYin. I’m born and raised in mainland China, so Pinyin is my system of choice. (ZhuYin, on the other hand, &lt;em&gt;esto me suena a chino&lt;/em&gt;.) Therefore, it should be no surprise to you that, for the Chinese language specifically, there is &lt;strong&gt;yet another layer of framework&lt;/strong&gt; (or “meta-engine”, if you like) sitting between Fcitx/IBus and the actual engines. &lt;strong&gt;We are talking about &lt;/strong&gt;&lt;a href="https://rime.im/"&gt;&lt;strong&gt;Rime (中州韻輸入法引擎)&lt;/strong&gt;&lt;/a&gt;&lt;strong&gt; here.&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;As an input method engine, Rime itself doesn’t care about how the “candidate words” panel should be rendered, so it exists in the Linus world mainly as wrappers &lt;a href="https://github.com/rime/home/wiki/RimeWithIBus"&gt;for IBus&lt;/a&gt; and for &lt;a href="https://github.com/fcitx/fcitx5-rime"&gt;Fcitx(5)&lt;/a&gt;. To install the latter, run (assuming ArchLinux):&lt;/p&gt;&lt;pre&gt;yay -Syu fcitx5-rime&lt;/pre&gt;&lt;p&gt;Rime runs “input schemas”. An input schema defines how a particular sequence Latin keystrokes should be mapped to Chinese candidate words. Many input schemas exist (see &lt;a href="https://wiki.archlinux.org/title/Rime#Configuration"&gt;this page on ArchLinux wiki&lt;/a&gt;). I recommend &lt;a href="https://dvel.me/posts/rime-ice/"&gt;雾凇拼音 (&lt;/a&gt;&lt;a href="https://dvel.me/posts/rime-ice/"&gt;rime-ice)&lt;/a&gt;.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/742/1*-XoQcBNyesv7I8p_TfbhJQ.png" /&gt;&lt;figcaption&gt;Image from &lt;a href="https://github.com/iDvel/rime-ice"&gt;the official GitHub repo&lt;/a&gt;.&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;To install this input schema, download and run &lt;a href="https://github.com/Mark24Code/rime-auto-deploy"&gt;this auto-deployment script&lt;/a&gt;:&lt;/p&gt;&lt;pre&gt;git clone --depth=1 https://github.com/Mark24Code/rime-auto-deploy.git --branch latest&lt;br /&gt;cd rime-auto-deploy&lt;br /&gt;./installer.rb&lt;/pre&gt;&lt;p&gt;Now you should be good to go. Enjoy typing in Linux. Here’s to endless inspiration and creativity!&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=dbfaea3496fc" width="1" /&gt;</description><author>Stories by Ming on Medium</author><pubDate>Sun, 22 Dec 2024 00:50:55 GMT</pubDate><guid isPermaLink="true">https://lmy.medium.com/linux-zhongwen-shurufa-dbfaea3496fc?source=rss-7e8adfcc60fb------2</guid></item><item><title>How to get a Driver's License in California</title><link>https://thatxliner.github.io/posts/how-to-get-a-drivers-license-in-california/</link><description>A guide for my friends who are starting to get their driver's license</description><author>ThatXliner's Blog</author><pubDate>Sat, 21 Dec 2024 22:45:21 GMT</pubDate><guid isPermaLink="true">https://thatxliner.github.io/posts/how-to-get-a-drivers-license-in-california/</guid></item><item><title>The books I enjoyed the most in 2024</title><link>https://blog.ovalerio.net/archives/3051</link><description>Another year went by, and another batch of books was consumed. Just like I did last year, I want to share the ones that I enjoyed the most. But what kind of metric is that? Truth be told, it is not an objective one. Last year, I clearly described it like this: I don’t mean [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Sat, 21 Dec 2024 21:06:52 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/3051</guid></item><item><title>180 Days…</title><link>https://digitalnomadder.micro.blog/2024/12/21/days.html</link><description>&lt;p&gt;When we first bought our &lt;a href="https://digitalnomadder.micro.blog/2022/08/24/201336.html"&gt;condotel&lt;/a&gt;, we were told that we could only stay in our unit up to 180 days.&lt;/p&gt;
&lt;p&gt;I looked at our paperwork, and everything we signed said “unlimited use as owners”.&lt;/p&gt;
&lt;p&gt;We tried to book our stay for a year and we were told that we can’t stay more than 180 consecutive days.&lt;/p&gt;
&lt;p&gt;Now, we have to pack everything we own in suitcases every 180 days, vacate our unit, store our belongings either in the car or with the bellman service and leave for a few days.&lt;/p&gt;
&lt;img alt="" height="600" src="https://cdn.uploads.micro.blog/79953/2024/95713441f9.jpg" width="337" /&gt;
&lt;p&gt;We usually schedule our move out as close to possible when we are planning to travel.&lt;/p&gt;
&lt;p&gt;It’s a slight irritation.  But we have it down to science&lt;/p&gt;</description><author>The Digital Nomad</author><pubDate>Sat, 21 Dec 2024 20:19:59 GMT</pubDate><guid isPermaLink="true">https://digitalnomadder.micro.blog/2024/12/21/days.html</guid></item><item><title>OpenAI o3 Breakthrough High Score on ARC-AGI-Pub</title><link>https://heidenstedt.org/links/oai-o3-pub-breakthrough/</link><description>&lt;p&gt;
      &lt;em&gt;Best viewed on the &lt;a href="https://heidenstedt.org/links/oai-o3-pub-breakthrough/"&gt;original page&lt;/a&gt;, where extended functionality like the
    footnote helper is available.&lt;/em&gt;
    &lt;/p&gt;&lt;p&gt;&lt;a href="https://arcprize.org/blog/oai-o3-pub-breakthrough"&gt;This&lt;/a&gt; is a article that explores the capabilities of OpenAI&amp;rsquo;s o3 model. &lt;a href="https://news.ycombinator.com/item?id=42473321"&gt;HN&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The impacts of AI reasoning are getting closer and closer to surpassing human capabilities.&lt;br /&gt;
But running it to solve a problem is extremely expensive.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Effectively, o3 represents a form of deep learning-guided program search. The model does test-time search over a space of &amp;ldquo;programs&amp;rdquo; (in this case, natural language programs – the space of CoTs that describe the steps to solve the task at hand), guided by a deep learning prior (the base LLM). The reason why solving a single ARC-AGI task can end up taking up tens of millions of tokens and cost thousands of dollars is because this search process has to explore an enormous number of paths through program space – including backtracking.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;(programs is defined here as &amp;ldquo;My mental model for LLMs is that they work as a repository of vector programs&amp;rdquo;)&lt;/p&gt;
&lt;p&gt;As far as i understand this and it&amp;rsquo;s implications i appears to me this process is more like a directed brute force with many candidates and selects the best one.&lt;br /&gt;
There is a &lt;a href="https://news.ycombinator.com/item?id=42479422"&gt;thread&lt;/a&gt; on HN that discusses this topic.&lt;/p&gt;
&lt;h2 id="gpt-4-summary"&gt;&lt;a href="https://heidenstedt.org/links/oai-o3-pub-breakthrough/#gpt-4-summary"&gt;GPT-4 Summary&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;OpenAI&amp;rsquo;s new &lt;strong&gt;o3 system&lt;/strong&gt; has achieved a groundbreaking &lt;strong&gt;75.7% on the ARC-AGI-Pub Semi-Private Evaluation&lt;/strong&gt; within a $10k compute limit, surpassing previous models. A high-compute version scored &lt;strong&gt;87.5%&lt;/strong&gt;, marking a major step in AI&amp;rsquo;s adaptability to novel tasks. This success highlights a shift from scaling existing architectures to innovative mechanisms for generalization and test-time knowledge recombination.&lt;/p&gt;
&lt;p&gt;Key achievements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ARC-AGI performance&lt;/strong&gt;: o3 shows unprecedented adaptability, unlike previous GPT-family models, which struggled with novel tasks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efficiency challenges&lt;/strong&gt;: Low-compute costs $17–$20 per task but high-compute performance remains expensive and exploratory.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Core innovation&lt;/strong&gt;: o3 employs &lt;strong&gt;natural language program search&lt;/strong&gt;, generating and executing task-specific solutions during runtime, guided by deep learning priors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Despite its advancements, o3 is not AGI, as it still fails simple tasks. Upcoming benchmarks, such as &lt;strong&gt;ARC-AGI-2 in 2025&lt;/strong&gt;, aim to further challenge AI systems while encouraging open-source progress.&lt;/p&gt;
&lt;p&gt;This breakthrough underscores a qualitative leap in AI research, reshaping paths toward AGI and sparking broader scientific engagement.&lt;/p&gt;</description><author>Mia Heidenstedt</author><pubDate>Sat, 21 Dec 2024 16:51:53 GMT</pubDate><guid isPermaLink="true">https://heidenstedt.org/links/oai-o3-pub-breakthrough/</guid></item><item><title>Washboard abs, great hires, and market dominance</title><link>https://dylanfitzgerald.net/blog/washboard-abs-great-hires-and-market-dominance/</link><description>&lt;blockquote&gt;
&lt;p&gt;Abs are made in the kitchen, championships are won on the training ground, and a sustainable body of creative work is built on a foundation of the creator&amp;rsquo;s psychological self-acceptance.&lt;/p&gt;
&lt;div class="CITE"&gt;
&lt;p&gt;&lt;a href="https://visakanv.com/"&gt;Visakan Veerasamy&lt;/a&gt;, &lt;em&gt;&amp;ldquo;I don&amp;rsquo;t wanna!&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Most of what we celebrate are trailing indicators. They&amp;rsquo;re exciting! A major product launch, lapping your competitors. Pulling a major hire into your team and watching them soar. Giving a successful talk at a conference.&lt;/p&gt;
&lt;p&gt;These are great events, and we should celebrate them!&lt;/p&gt;
&lt;p&gt;They are &lt;em&gt;terrible&lt;/em&gt; expectations.&lt;/p&gt;
&lt;p&gt;If you put in your work each day with the sole expectation of winning the market in 3 years, you&amp;rsquo;re going to flounder, burn out, and likely fail&amp;ndash;and should none of those apply, you&amp;rsquo;re still counting on some luck.&lt;/p&gt;
&lt;p&gt;If you instead spend each day looking for where you can do slightly better at one thing than the day before, and do it &lt;span class="underline"&gt;each and every day&lt;/span&gt;, with the expectation of getting slightly better&amp;hellip;you&amp;rsquo;re going to get better at that thing. With time, the difference will be dramatic.&lt;/p&gt;
&lt;p&gt;And you might not end up with the abs or the championship or the one perfect position or hire or owning the market. You&amp;rsquo;ll probably get some big wins mixed in there, too. They&amp;rsquo;ll be trailing indicators, and they&amp;rsquo;ll feel great, and they won&amp;rsquo;t be what you expected.&lt;/p&gt;
&lt;p&gt;And when you&amp;rsquo;re done celebrating, it&amp;rsquo;ll be time to get back to setting expectations around what you can do better tomorrow.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Editor&amp;rsquo;s note: Ctrl+Shift will be sporadic through the next few weeks, resuming in earnest in 2025. Happy holidays and new year to all!&lt;/p&gt;</description><author>Index on Dylan Fitzgerald</author><pubDate>Sat, 21 Dec 2024 05:36:00 GMT</pubDate><guid isPermaLink="true">https://dylanfitzgerald.net/blog/washboard-abs-great-hires-and-market-dominance/</guid></item><item><title>Handling keyboard shortcuts in JavaScript</title><link>https://ricardoanderegg.com/posts/handle-keyboard-shortcuts-javascript/</link><description>&lt;p&gt;I was recently reading the source code of &lt;code&gt;mizu.js&lt;/code&gt;&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; and I liked the utility &amp;ldquo;function&amp;rdquo;&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; they have to handle keyboard shortcuts/combinations. I decided to slightly modify it to have it as part of my JavaScript utility functions.&lt;/p&gt;
&lt;h2 id="the-function"&gt;The function&lt;/h2&gt;
&lt;p&gt;This function returns another function itself. You pass in a string that represents a combination of key presses like &lt;code&gt;Shift+Alt+C&lt;/code&gt;, and it returns a function that will return &lt;code&gt;true&lt;/code&gt; if a &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent"&gt;KeyboardEvent&lt;/a&gt; matches that combination. I had to make some modifications to the original code. First, I converted it to JavaScript, because I don&amp;rsquo;t like having a build step, and I want to be able to copy-paste this code. I also changed the matcher on the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key"&gt;event.key&lt;/a&gt; to match on &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/code"&gt;event.code&lt;/a&gt; because I found &lt;code&gt;event.key&lt;/code&gt; was returning a string generated by the keyboard combination, not the actual key pressed.&lt;/p&gt;</description><author>Posts on rand[om]</author><pubDate>Sat, 21 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ricardoanderegg.com/posts/handle-keyboard-shortcuts-javascript/</guid></item><item><title>Cramming scrapscript into msgpack</title><link>https://taylor.town/flat-scraps-000</link><description>Every scrapscript program can be crammed into "flat" scraps.</description><author>taylor.town</author><pubDate>Sat, 21 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/flat-scraps-000</guid></item><item><title>Summary: The Tipping Point by Malcolm Gladwell</title><link>https://www.chestergrant.com/summary-the-tipping-point-by-malcolm-gladwell</link><description>&lt;div class="posthaven-post-body"&gt;&lt;p&gt;&lt;/p&gt;&lt;center&gt;        &lt;div class="posthaven-gallery" id="posthaven_gallery[2184140]"&gt;
                  &lt;p class="posthaven-file posthaven-file-image posthaven-file-state-processed"&gt;
          &lt;img class="posthaven-gallery-image" src="https://phaven-prod.s3.amazonaws.com/files/image_part/asset/3262413/URzObnkiI62hisSp_3Hm1kc8RyA/medium_Cover.png" /&gt;
        &lt;/p&gt;

        &lt;/div&gt;
&lt;/center&gt;&lt;b&gt;1. The Tipping Point is the biography of an idea, and the idea is very simple. Ideas and products and messages and behaviors spread just like viruses do.&lt;/b&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;2.  Three characteristics— one, contagiousness; two, the fact that little causes can have big effects; and three, that change happens not gradually but at one dramatic moment&lt;/b&gt;&lt;/p&gt;&lt;p&gt;3. The name given to that one dramatic moment in an epidemic when everything can change all at once is the Tipping Point.&lt;/p&gt;&lt;p&gt;&lt;b&gt;4. And when an epidemic tips, when it is jolted out of equilibrium, it tips because something has happened, some change has occurred in one (or two or three) of those areas. These three agents of change I call the Law of the Few, the Stickiness Factor, and the Power of Context.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;5. When it comes to epidemics, though, this disproportionality becomes even more extreme: a tiny percentage of people do the majority of the work.&lt;/p&gt;&lt;p&gt;6.  In all of the city of Colorado Springs— a town of well in excess of 100,000 people— the epidemic of gonorrhea tipped because of the activities of 168 people living in four small neighborhoods and basically frequenting the same six bars. Who were those 168 people? They aren’t like you or me. They are people who go out every night, people who have vastly more sexual partners than the norm, people whose lives and behavior are well outside of the ordinary.&lt;/p&gt;&lt;p&gt;7. This idea of the importance of stickiness in tipping has enormous implications for the way we regard social epidemics as well. We tend to spend a lot of time thinking about how to make messages more contagious— how to reach as many people as possible with our products or ideas. But the hard part of communication is often figuring out how to make sure a message doesn’t go in one ear and out the other. Stickiness means that a message makes an impact. You can’t get it out of your head. It sticks in your memory.&lt;/p&gt;&lt;p&gt;8. The Stickiness Factor says that there are specific ways of making a contagious message memorable; there are relatively simple changes in the presentation and structuring of information that can make a big difference in how much of an impact it makes.&lt;/p&gt;&lt;p&gt;9. The Power of Context says that human beings are a lot more sensitive to their environment than they may seem.&lt;/p&gt;&lt;p&gt;&lt;b&gt;10. The answer is that the success of any kind of social epidemic is heavily dependent on the involvement of people with a particular and rare set of social gifts: Connectors, Mavens, and Salesmen.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;11. Most of us don’t have particularly broad and diverse groups of friends. In one well- known study, a group of psychologists asked people living in the Dyckman public housing project in northern Manhattan to name their closest friend in the project; 88 percent of the friends lived in the same building, and half lived on the same floor. In general, people chose friends of similar age and race.&lt;/p&gt;&lt;p&gt;12. But if the friend lived down the hall, then age and race became a lot less important. Proximity overpowered similarity.&lt;/p&gt;&lt;p&gt;13. We’re friends with the people we do things with, as much as we are with the people we resemble. We don’t seek out friends, in other words. We associate with the people who occupy the same small, physical spaces that we do. &lt;/p&gt;&lt;p&gt;14. The answer is that in the six degrees of separation, not all degrees are equal. When Milgram analyzed his experiment, for example, he found that many of the chains from Omaha to Sharon followed the same asymmetrical pattern. Twenty- four letters reached the stockbroker at his home in Sharon, and of those, sixteen were given to him by the same person, a clothing merchant Milgram calls Mr. Jacobs.&lt;/p&gt;&lt;p&gt;15. The balance of letters came to the stockbroker at his office, and of those the majority came through two other men, whom Milgram calls Mr. Brown and Mr. Jones. In all, half of the responses that came back to the stockbroker were delivered to him by these same three people.&lt;/p&gt;&lt;p&gt;&lt;b&gt;16. Six degrees of separation doesn’t mean that everyone is linked to everyone else in just six steps. It means that a very small number of people are linked to everyone else in a few steps, and the rest of us are linked to the world through those special few.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;17. These people who link us up with the world, who bridge Omaha and Sharon, who introduce us to our social circles— these people on whom we rely more heavily than we realize— are Connectors, people with a special gift for bringing the world together.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;18. He found that 56 percent of those he talked to found their job through a personal connection. Another 18.8 percent used formal means— advertisements, headhunters— and roughly 20 percent applied directly. This much is not surprising; the best way to get in the door is through a personal contact. But, curiously, Granovetter found that of those personal connections, the majority were “weak ties.” Of those who used a contact to find a job, only 16.7 percent saw that contact “often”— as they would if the contact were a good friend— and 55.6 percent saw their contact only “occasionally.” Twenty- eight percent saw the contact “rarely.” People weren’t getting their jobs through their friends. They were getting them through their acquaintances.&lt;/p&gt;&lt;p&gt;19. Word- of- mouth epidemics are the work of Connectors.&lt;/p&gt;&lt;p&gt;20. Paul Revere was a Connector. But he was also— and this is the second of the three kinds of people who control word- of- mouth epidemics— a Maven. &lt;b&gt;The word Maven comes from the Yiddish, and it means one who accumulates knowledge.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;21. The critical thing about Mavens, though, is that they aren’t passive collectors of information. It isn’t just that they are obsessed with how to get the best deal on a can of coffee. What sets them apart is that once they figure out how to get that deal, they want to tell you about it too.&lt;/p&gt;&lt;p&gt;22. Mavens have the knowledge and the social skills to start word- of- mouth epidemics. What sets Mavens apart, though, is not so much what they know but how they pass it along. The fact that Mavens want to help, for no other reason than because they like to help&lt;/p&gt;&lt;p&gt;&lt;b&gt;23. A Connector might tell ten friends where to stay in Los Angeles, and half of them might take his advice. A Maven might tell five people where to stay in Los Angeles but make the case for the hotel so emphatically that all of them would take his advice. These are different personalities at work, acting for different reasons. But they both have the power to spark word- of- mouth epidemics.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;24. In a social epidemic, Mavens are data banks. They provide the message. Connectors are social glue: they spread it. But there is also a select group of people— Salesmen— with the skills to persuade us when we are unconvinced of what we are hearing, and they are as critical to the tipping of word- of- mouth epidemics as the other two groups. Who are these Salesmen? And what makes them so good at what they do?&lt;/p&gt;&lt;p&gt;25. If we think about emotion this way— as outside- in, not inside- out— it is possible to understand how some people can have an enormous amount of influence over others. Some of us, after all, are very good at expressing emotions and feelings, which means that we are far more emotionally contagious than the rest of us. Psychologists call these people “senders.” Senders have special personalities.&lt;/p&gt;&lt;p&gt;26. They are also physiologically different. Scientists who have studied faces, for example, report that there are huge differences among people in the location of facial muscles, in their form, and also— surprisingly— even in their prevalence. “It is a situation not unlike in medicine,” says Cacioppo. “There are carriers, people who are very expressive, and there are people who are especially susceptible. It’s not that emotional contagion is a disease. But the mechanism is the same.”&lt;/p&gt;&lt;p&gt;27. Only the charismatic person could infect the other people in the room with his or her emotions.&lt;/p&gt;&lt;p&gt;28. The Law of the Few says that there are exceptional people out there who are capable of starting epidemics. All you have to do is find them.&lt;/p&gt;&lt;p&gt;29. The lesson of stickiness is the same. There is a simple way to package information that, under the right circumstances, can make it irresistible. All you have to do is find it.&lt;/p&gt;&lt;p&gt;30. Wilson and Kelling argued that crime is the inevitable result of disorder. If a window is broken and left unrepaired, people walking by will conclude that no one cares and no one is in charge. Soon, more windows will be broken, and the sense of anarchy will spread from the building to the street on which it faces, sending a signal that anything goes. In a city, relatively minor problems like graffiti, public disorder, and aggressive panhandling, they write, are all the equivalent of broken windows, invitations to more serious crimes: Muggers and robbers, whether opportunistic or professional, believe they reduce their chances of being caught or even identified if they operate on streets where potential victims are already intimidated by prevailing conditions. If the neighborhood cannot keep a bothersome panhandler from annoying passersby, the thief may reason, it is even less likely to call the police to identify a potential mugger or to interfere if the mugging actually takes place.&lt;/p&gt;&lt;p&gt;31. This is an epidemic theory of crime. It says that crime is contagious— just as a fashion trend is contagious— that it can start with a broken window and spread to an entire community.&lt;/p&gt;&lt;p&gt;32. Broken Windows theory and the Power of Context are one and the same. They are both based on the premise that an epidemic can be reversed, can be tipped, by tinkering with the smallest details of the immediate environment.&lt;/p&gt;&lt;p&gt;33. Psychologists call this tendency the Fundamental Attribution Error (FAE), which is a fancy way of saying that when it comes to interpreting other people’s behavior, human beings invariably make the mistake of overestimating the importance of fundamental character traits and underestimating the importance of the situation and context.&lt;/p&gt;&lt;p&gt;34. There is something in all of us that makes us instinctively want to explain the world around us in terms of people’s essential attributes: he’s a better basketball player, that person is smarter than I am.&lt;/p&gt;&lt;p&gt;35. Judith Harris has convincingly argued that peer influence and community influence are more important than family influence in determining how children turn out. Studies of juvenile delinquency and high school drop- out rates, for example, demonstrate that a child is better off in a good neighborhood and a troubled family than he or she is in a troubled neighborhood and a good family.&lt;/p&gt;&lt;p&gt;36. Wesley realized that if you wanted to bring about a fundamental change in people’s belief and behavior, a change that would persist and serve as an example to others, you needed to create a community around them, where those new beliefs could be practiced and expressed and nurtured.&lt;/p&gt;&lt;p&gt;37. Dunbar has actually developed an equation, which works for most primates, in which he plugs in what he calls the neocortex ratio of a particular species— the size of the neocortex relative to the size of the brain— and the equation spits out the expected maximum group size of the animal. If you plug in the neocortex ratio for Homo sapiens, you get a group estimate of 147.8— or roughly 150. “The figure of 150 seems to represent the maximum number of individuals with whom we can have a genuinely social relationship, the kind of relationship that goes with knowing who they are and how they relate to us.&lt;/p&gt;&lt;p&gt;38. Dunbar has combed through the anthropological literature and found that the number 150 pops up again and again. For example, he looks at 21 different hunter- gatherer societies for which we have solid historical evidence, from the Walbiri of Australia to the Tauade of New Guinea to the Ammassalik of Greenland to the Ona of Tierra del Fuego and found that the average number of people in their villages was 148.4.&lt;/p&gt;&lt;p&gt;39. Over the years military planners have arrived at a rule of thumb which dictates that functional fighting units cannot be substantially larger than 200 men,” Dunbar writes. “This, I suspect, is not simply a matter of how the generals in the rear exercise control and coordination, because companies have remained obdurately stuck at this size despite all the advances in communications technology since the first world war.&lt;/p&gt;&lt;p&gt;40. Then there is the example of the religious group known as the Hutterites, who for hundreds of years have lived in self- sufficient agricultural colonies in Europe and, since the early twentieth century, in North America. The Hutterites (who came out of the same tradition as the Amish and the Mennonites) have a strict policy that every time colony approaches 150, they split it in two and start a new one. “Keeping things under 150 just seems to be the best and most efficient way to manage a group of people,” Bill Gross, one of the leaders of a Hutterite colony outside Spokane told me. “When things get larger than that, people become strangers to one another.”&lt;/p&gt;&lt;p&gt;41. The Rule of 150 suggests that the size of a group is another one of those subtle contextual factors that can make a big difference.&lt;/p&gt;&lt;p&gt;42. Heavy smokers have been shown to have a much greater sex drive than nonsmokers. They are more sexually precocious; they have a greater “need” for sex, and greater attraction to the opposite sex.&lt;/p&gt;&lt;p&gt;43. At age nineteen, for example, 15 percent of nonsmoking white women attending college have had sex. The same number for white female college students who do smoke is 55 percent.&lt;/p&gt;&lt;p&gt;44. Contagiousness is in larger part a function of the messenger. Stickiness is primarily a property of the message.&lt;/p&gt;&lt;p&gt;45. In a series of large and well-designed studies of twins — particularly twins separated at birth and reared apart — geneticists have shown that most of the character traits that make us who we are — friendliness, extroversion, nervousness, openness, and so on — are about half determined by our genes and half determined by our environment, and the assumption has always been that this environment that makes such a big difference in our lives is the environment of the home. The problem is, however, that whenever psychologists have set out to look for this nurture effect, they can’t find it.&lt;/p&gt;&lt;p&gt;46. On things like measures of intellectual ability and certain aspects of personality, the biological children are fairly similar to their parents. For the adopted kids, however, the results are downright strange. Their scores have nothing whatsoever in common with their adoptive parents: these children are no more similar in their personality or intellectual skills to the people who raised them, fed them, clothed them, read to them, taught them, and loved them for sixteen years than they are to any two adults taken at random off the street.&lt;/p&gt;&lt;p&gt;47. Two years later, Columbia University psychologist Alexander Glassman discovered that 60 percent of the heavy smokers he was studying as part of an entirely different research project had a history of major depression. About 80 percent of alcoholics smoke. Close to 90 percent of schizophrenics smoke.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;/div&gt;</description><author>Chester Grant</author><pubDate>Fri, 20 Dec 2024 23:55:49 GMT</pubDate><guid isPermaLink="true">https://www.chestergrant.com/summary-the-tipping-point-by-malcolm-gladwell</guid></item><item><title>First month in my new home</title><link>https://liza.io/first-month-in-my-new-home/</link><description>&lt;p&gt;It&amp;rsquo;s been exactly one month since I moved to Uppsala. I&amp;rsquo;m still adjusting and discovering my routines here, but here&amp;rsquo;s what I&amp;rsquo;ve been up to so far.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Fri, 20 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/first-month-in-my-new-home/</guid></item><item><title>The IPv6 situation on Docker is good now!</title><link>https://ounapuu.ee/posts/2024/12/20/docker-ipv6/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/12/20/docker-ipv6/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;Good news, everyone! Doing IPv6 networking stuff on Docker is actually good now!&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve recently started reworking my home server setup to be more IPv6 compatible, and as part of that I learned that
during
the summer of 2024 &lt;a href="https://docs.docker.com/engine/release-notes/27/#ipv6"&gt;Docker shipped an update&lt;/a&gt; that eliminated a
lot of the configuration and tweaking previously necessary
to support IPv6.&lt;/p&gt;
&lt;p&gt;There is no need to change the daemon configuration any longer, it just works on Docker Engine v27 and later.&lt;/p&gt;
&lt;h3 id="examples"&gt;Examples&lt;/h3&gt;
&lt;p&gt;If your host has a working IPv6 setup and you want to listen to port 80 on both IPv4 and IPv6, then you don&amp;rsquo;t
have to do anything special. However, the container will only have an IPv4 address internally.
You can verify it by listing all the Docker networks via &lt;code&gt;sudo docker network ls&lt;/code&gt; and running
&lt;code&gt;sudo docker network inspect network-name-here&lt;/code&gt; for the one associated with your container.&lt;/p&gt;
&lt;p&gt;For services like &lt;code&gt;nginx&lt;/code&gt; that log the source IP address, this is problematic, as every incoming IPv6 request will be
logged with the Docker network gateway IP address, such as &lt;code&gt;10.88.0.1&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;name: nginx
services:
  nginx:
    container_name: nginx
    ports:
      - 80:80
    image: docker.io/library/nginx
    restart: always
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you want the container to have an IPv4 &lt;em&gt;and&lt;/em&gt; an IPv6 address within the Docker network, you can create a new network
and enable IPv6 in it.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;name: nginx
services:
  nginx:
    container_name: nginx
    networks:
      - nginx-network
    ports:
      - 80:80
    image: docker.io/library/nginx
    restart: always
networks:
  nginx-network:
    enable_ipv6: true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;There are situations where it&amp;rsquo;s handy to have a static IP address for a container within the Docker network.
If you need help coming up with an unique local IPv6 address range, you
can &lt;a href="https://unique-local-ipv6.com/"&gt;use this tool.&lt;/a&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;name: nginx
services:
  nginx:
    container_name: nginx
    networks:
      nginx-network
        ipv4_address: 10.69.42.5
        ipv6_address: fdec:cc68:5178::abba
    ports:
      - 80:80
    image: docker.io/library/nginx
    restart: always
networks:
  nginx-network:
    enable_ipv6: true
    ipam:
      driver: default
      config:
        - subnet: "10.69.42.0/24"
        - subnet: "fdec:cc68:5178::/64"
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you choose the &lt;a href="https://docs.docker.com/engine/network/drivers/host/"&gt;host network driver,&lt;/a&gt; your container will
operate within the same networking space as your container host. If the host handles both IPv4 and IPv6 networking, then
your container will happily operate with both. However, due to reduced network isolation, this has some security
implications that you must take into account.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;name: nginx
services:
  nginx:
    container_name: nginx
    network_mode: host
    # ports are not relevant with host network mode
    image: docker.io/library/nginx
    restart: always
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you want your container to only accept connections on select interfaces, such as a Wireguard connection, then you will need
to specify the IP addresses in the &lt;code&gt;ports&lt;/code&gt; section. Here&amp;rsquo;s one example with both IPv4 and IPv6.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;name: nginx
services:
  nginx:
    container_name: nginx
    networks:
      - nginx-network
    ports:
      - 10.69.42.5:80:80
      - "[fdec:cc68:5178::beef]:80:80"
    image: docker.io/library/nginx
    restart: always
networks:
  nginx-network:
    enable_ipv6: true
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="what-about-podman"&gt;What about Podman?&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve given up on Podman. Before doing things the IPv6 way, Podman was functional for the most part, requiring a few
tweaks to get things working.&lt;/p&gt;
&lt;p&gt;I have not managed to get Podman to play fair with IPv6. No matter what I did, I could not get it to listen to certain
ports and access my services, the ports would always be filtered out.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m genuinely happy to see that the IPv6 support has gotten better with Docker, and I hope that this short introduction
helps those out there looking to do things the IPv6 way with containers.&lt;/p&gt;</description><author>./techtipsy</author><pubDate>Fri, 20 Dec 2024 19:30:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/12/20/docker-ipv6/</guid></item><item><title>Reviewing Tomorrow, and Tomorrow, and Tomorrow by Gabrielle Zevin</title><link>https://davi.sh/reading/2024/tomorrow3/</link><description>&lt;p&gt;A book about life, love, and video games. I honestly didn’t know what I was getting into
when I started this book, but by the time they described the twist behind Sadie’s first
video game in the second chapter I was hooked. Some people may be put off by how the
characters and the third-person narrator talk about video games as a high art form but I
love hearing and seeing how digital media can expand our artistic horizons rather than
shrink them.&lt;/p&gt;
&lt;p&gt;A friend who read the book around the same time described it as “a romance, I guess?” and
that ambiguity is at the heart of what makes this book feel real. As we follow the main
characters from their college days into their thirties, the ways their views about the
world and each other change does a lot towards making the characters feel like real
people.&lt;/p&gt;
&lt;p&gt;Ultimately I got a lot out of what this book had to say about friendship. It’s natural for
miscommunications to accumulate into misunderstandings over the years despite our best
intentions. What’s important in the end is to remember that it’s never too late to return
to the foundation and try to grow something new.&lt;/p&gt;</description><author>Davis Haupt's Blog</author><pubDate>Fri, 20 Dec 2024 19:00:00 GMT</pubDate><guid isPermaLink="true">https://davi.sh/reading/2024/tomorrow3/</guid></item><item><title>2024 Our commitment to resource feminist tech at the frontlines</title><link>https://numun.fund/2024-our-commitment-to-resource-feminist-tech-at-the-frontlines/</link><description>Dear community, We close this year with many feelings &amp;#8211; gratitude and joy for being able to have been in community with many of our partners, nodes and constellations of activists at different spaces throughout the year; grave reflection on the many ways that technology is implicated in how we are experiencing the surfacing of [&amp;#8230;]</description><author>Numun Fund</author><pubDate>Fri, 20 Dec 2024 18:37:59 GMT</pubDate><guid isPermaLink="true">https://numun.fund/2024-our-commitment-to-resource-feminist-tech-at-the-frontlines/</guid></item><item><title>Defending Farm Animal Sanctuaries from Effective Altruism</title><link>https://joshbaldwin.substack.com/p/defending-farm-animal-sanctuaries</link><description>This piece expands on the content of The Power Of Farm Animal Sanctuaries by defending the work of farm animal sanctuaries from the principles of Effective Altruism.</description><author>Josh Baldwin</author><pubDate>Fri, 20 Dec 2024 17:38:06 GMT</pubDate><guid isPermaLink="true">https://joshbaldwin.substack.com/p/defending-farm-animal-sanctuaries</guid></item><item><title>De Honoris Numquam Satis: Reflections on Snobbery in Academia</title><link>https://bastian.rieck.me/blog/2024/satis/</link><description>&lt;p&gt;One of my more endearing characteristics&amp;mdash;or so I like to believe&amp;mdash;is
my naïveté in the face of academic reality. It is probably amusing to
those who know me personally to see how I just do not &amp;lsquo;grok&amp;rsquo; certain
things, making me either bewildered, angry, sad, or a linear combination
thereof. One of the many, many, many things that fall into this category
is the tendency of &lt;em&gt;some&lt;/em&gt; academics to, in their hunger for more honour
and prestige, make devilish compromises.&lt;/p&gt;
&lt;p&gt;Let me explain! As you might know, everyone tries to measure academic
output by ludicrous indices. While this is mostly nothing but modern
&lt;a href="https://en.wikipedia.org/wiki/Haruspex"&gt;haruspicy&lt;/a&gt;, combined with
a thin veneer of plausibility, some universities and stakeholders dig
this stuff and lap it right up. Every year, academia goes crazy while
companies like &lt;a href="https://en.wikipedia.org/wiki/Clarivate"&gt;Clarivate&lt;/a&gt;
emerge from their lairs and announce what they read in this year&amp;rsquo;s
entrails. You then get the dubious honour of appearing on lists like the
one of &lt;a href="https://clarivate.com/highly-cited-researchers"&gt;highly-cited researchers&lt;/a&gt;,
a list that does not even include mathematics or machine learning.
I guess those fields were not deemed to be scientific enough&amp;mdash;but
I digress. Suffice it to say, everyone loves to count citations as the
measure of success, culminating in absurd indices like the
&lt;a href="https://en.wikipedia.org/wiki/H-index"&gt;h-index&lt;/a&gt;, of which much has been
written elsewhere, and so I shall not start another rant on my own. The
two things for you, dear reader, to understand about the h-index are,
to wit:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Larger h-indices are &lt;em&gt;obviously&lt;/em&gt; directly equivalent to your worth as
a researcher, and, by extension, your value as a human being.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;The h-index is a function of your publications; you need $k$
publications with at least $k$ citations to have an h-index of $k$.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This leads the more, shall we say, incentive-driven senior academics to
adopt an interesting strategy: They will force their junior researchers
to put them on &lt;strong&gt;any and all papers possible&lt;/strong&gt;. Let us take another
brief explanatory digression here: Science is foremost a &lt;em&gt;conversation&lt;/em&gt;
about potential facts. To participate in this conversations, budding
scientists are expected to publish. Each publication has an author list,
and, depending on your field, the author list conveys additional
information about the relevance of each individual author. For instance,
in computer science, it is typically the first author(s) who are more
junior and did most of the experiments or implementations, while the
last author(s) are typically acting in a more supervisory capacity. As
someone who is now in the position of such a senior author, I can spill
the beans and mention that when I am the last author, I typically
focus most of my energies in the project on discussing ideas with
students, co-writing the paper with them, critiquing their drafts or
code, as opposed to coding large parts by myself. This is because
I believe that Ph.D. students should be considered apprentices who are
supposed to excel in their craft, meaning that supervisors need to guide
them, but not leave them to their own devices.&lt;/p&gt;
&lt;p&gt;With the view of an apprenticeship in mind, it becomes abundantly clear
to me that, if a student has their own project cooking in which I am not
involved, said student can do publish their results without me. Or, in
other words, while I believe my very presence in the lab to be a balm
for anyone&amp;rsquo;s soul, and an inspiring presence to boot,&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; capable of
lifting the spirits of everyone and getting them unstuck, even my ego
knows its bounds. Essentially, since I picked excellent people to work
with me, I should not be surprised when they come up with great ideas on
their own and run with them. Hence, my dictum: &lt;strong&gt;As much as I would like
it to be true, my inspiring presence does not entitle me to authorship.&lt;/strong&gt;&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Imagine my astonishment when I learned that some outwardly accomplished academics &lt;em&gt;force&lt;/em&gt;
their way on the author lists, in a transparent ploy to increase their
h-index but also to increase their prestige. Taken to the very
extreme&amp;mdash;and it often is by some people&amp;mdash;this results in some
academics &amp;lsquo;publishing&amp;rsquo;&lt;sup id="fnref:4"&gt;&lt;a class="footnote-ref" href="#fn:4"&gt;4&lt;/a&gt;&lt;/sup&gt; more than one paper per week. All so that
they can say, presumably while pointing towards their Google Scholar page, &amp;lsquo;Look on my works, ye Mighty, and despair!&amp;rsquo;
Alas, they might not know the end of Ozymandias, but what is even more
baffling to me is that such &amp;lsquo;honours&amp;rsquo; must feel hollow, no?&lt;/p&gt;
&lt;p&gt;I honestly do not know and understand it, and can only draw upon my
native language of German, which of course has a word for this general
class of behaviour, namely &lt;a href="https://de.wikipedia.org/wiki/Standesd%C3%BCnkel"&gt;Standesdünkel&lt;/a&gt;.
Some dictionaries want you to believe that it is professional snobbery,
but it is actually so much more, going back to a time when nobility
was everything, and commoners were nothing. In my mind, I hear
professional &lt;em&gt;Standesdünkel&lt;/em&gt; when professors, like the lords and ladies
of yore, &lt;strong&gt;automatically&lt;/strong&gt; consider authorship as a type of sinecure or
tithes. Moral considerations notwithstanding, at which point would the
folks that are doing this shake their heads and say &amp;lsquo;Enough is enough,
maybe from now on, I will just be on papers that I actually contributed
something to&amp;rsquo;? Because, make no mistake, this &lt;em&gt;Standesdünkel&lt;/em&gt; &lt;strong&gt;is&lt;/strong&gt;
hurting younger academics. It is hurting the Ph.D. student who has
a great idea on their own and pursues it, maybe even in their scarce free
time. It is hurting the postdoctoral researcher who leads a small team
of students into a new direction, without getting any input from the
professor. It is even hurting younger faculty members who need to put
the &lt;em&gt;éminence grise&lt;/em&gt; on everyone of their papers, just to be then told
in an evaluation that &amp;lsquo;Their research is not sufficiently different from
the research directions of their advisers.&amp;rsquo;&lt;/p&gt;
&lt;p&gt;So again, why behave like that? Does this honour not taste hollow? If
you have reached a triple-digit h-index and a triple-digit publication
list, is it not time to do without tithes?
As you can imagine, I have no answers to these questions. I just wonder
where it would stop&amp;mdash;would some senior folks believe to be entitled to
authorship because they &lt;em&gt;looked&lt;/em&gt; at a Ph.D. student once? When does the
entitlement end?&lt;/p&gt;
&lt;p&gt;In all honesty, I also have my misguided notions about my own academic prestige and
the honours bestowed upon me,&lt;sup id="fnref:5"&gt;&lt;a class="footnote-ref" href="#fn:5"&gt;5&lt;/a&gt;&lt;/sup&gt; but I am not willing to deviate from my principles
for what I conceive to be, ultimately, a hollow win at best. Moreover,
I do not believe you can &amp;lsquo;win&amp;rsquo; academia. It is, for all intents and
purposes, an infinite game, so the best you can hope for is that you get
to &lt;em&gt;keep playing&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Happy playing, until next time!&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Just to be sure: This &lt;strong&gt;is&lt;/strong&gt; definitely satirical!&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Think of the spells cast by your standard &lt;a href="https://en.wikipedia.org/wiki/Cleric_(Dungeons_%26_Dragons)"&gt;Cleric&lt;/a&gt; character in &lt;a href="https://en.wikipedia.org/wiki/Dungeons_%26_Dragons"&gt;Dungeons &amp;amp; Dragons&lt;/a&gt;. This might require another exegesis, which I shall save for another post.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;This is of course not categorically so; for instance, if we start
discussing a project and I actually &lt;em&gt;can&lt;/em&gt; contribute to it, and,
more importantly, the students &lt;em&gt;wants&lt;/em&gt; me to contribute to it,
I might have earned my spot on the author list.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;These are air quotes and they do some very heavy lifting.&amp;#160;&lt;a class="footnote-backref" href="#fnref:4"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;That is, I believe that more would always be better for me.&amp;#160;&lt;a class="footnote-backref" href="#fnref:5"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Fri, 20 Dec 2024 17:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2024/satis/</guid></item><item><title>Feminist tech: the live tissues between tech &amp;amp; our justice struggles. Closing remarks at the 15th AWID Forum.</title><link>https://numun.fund/feminist-tech-15th-awid-forum/</link><description>Speech by Jac sm Kee at the 15th AWID Forum closing plenary 6th December 2024 The way that technology has been threaded in this forum has been significantly different from all the previous forums I have been a part of. It&amp;#8217;s a lot more central, a lot more present, and woven through a lot more [&amp;#8230;]</description><author>Numun Fund</author><pubDate>Fri, 20 Dec 2024 02:41:22 GMT</pubDate><guid isPermaLink="true">https://numun.fund/feminist-tech-15th-awid-forum/</guid></item><item><title>How to Use PostgreSQL for Data Normalization</title><link>https://dylanpaulus.com/posts/2024/how-to-use-postgres-for-data-normalization/</link><description>&lt;p&gt;Managing a database is more than just storing information—it's about storing data efficiently, effectively, and balancing trade-offs. We can employ techniques like data normalization to optimize our data for maintainability and readability, or data denormalization to optimize for raw query speed. Data normalization is the process of breaking data down into structured tables to reduce duplication and make it easier to query and store by enforcing standardization.&lt;/p&gt;
&lt;p&gt;In this article, we'll take a look at what data normalization is, how to apply normal forms to achieve data normalization, challenges and tools for data normalization in PostgreSQL, tips on when to denormalize, and how TimescaleDB makes normalization easier.&lt;/p&gt;</description><author>Dylan Paulus' Blog</author><pubDate>Fri, 20 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://dylanpaulus.com/posts/2024/how-to-use-postgres-for-data-normalization/</guid></item><item><title>Will all our drugs come from China?</title><link>https://atelfo.github.io/2024/12/20/will-all-our-drugs-come-from-china.html</link><description/><author>Alex’s blog</author><pubDate>Fri, 20 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://atelfo.github.io/2024/12/20/will-all-our-drugs-come-from-china.html</guid></item><item><title>Template nesting in (text|html)/template</title><link>https://paul.totterman.name/posts/golang-base-template/</link><description>&lt;p&gt;Go includes a safe and somewhat performant templating library. But the best way
to structure code using &lt;a href="https://pkg.go.dev/html/template" rel="noopener" target="_blank"&gt;html/template&lt;/a&gt; has
eluded me for a long time.&lt;/p&gt;</description><author>Paul's page</author><pubDate>Fri, 20 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://paul.totterman.name/posts/golang-base-template/</guid></item><item><title>Modern Portfolio Theory II: Random portfolio coverage in log volatility—return space with a risk free asset</title><link>https://bytepawn.com/modern-portfolio-theory-random-portfolio-coverage-risk-free-asset.html</link><description>&lt;p&gt;In a previous article I simulated random portfolios using Monte Carlo methods for the 2023 daily closing prices of the 100 stocks constituting the Nasdaq-100. Here I add the risk-free asset to the portfolio and examine how it affects the coverage in log volatility—return space.&lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/logvolatility-rf-3.png" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 20 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/modern-portfolio-theory-random-portfolio-coverage-risk-free-asset.html</guid></item><item><title>Label-Studio: Annotate Text and Image Data for AI and ML training</title><link>https://saeedesmaili.com/notes/label-studio-annotate-text-and-image-data-for-ai-and-ml/</link><description>&lt;p&gt;A few months ago I used &lt;a href="https://streamlit.io/" target="_blank"&gt;streamlit&lt;/a&gt;
 to build a simple UI, so I can collect manually labeled data for a LLM fine-tuning task at work. Streamlit is fine, but the full process of creating a nice UI with required functionalities for data annotation and data storage management wasn&amp;rsquo;t trivial.&lt;/p&gt;
&lt;p&gt;Today I found out about &lt;a href="https://labelstud.io/" target="_blank"&gt;label-studio&lt;/a&gt;
 which is an easy to use framework (backend and frontend) for data annotation task. It provides various annotation templates for text, image, audio, and video data!&lt;/p&gt;</description><author>Saeed Esmaili</author><pubDate>Thu, 19 Dec 2024 23:42:00 GMT</pubDate><guid isPermaLink="true">https://saeedesmaili.com/notes/label-studio-annotate-text-and-image-data-for-ai-and-ml/</guid></item><item><title>Hugo Social Image Previews</title><link>https://www.danielcorin.com/til/hugo/social-image-previews/</link><description>Hugo Social Image Previews</description><author>Thought Eddies</author><pubDate>Thu, 19 Dec 2024 23:27:37 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/hugo/social-image-previews/</guid></item><item><title>Quickly Filter and Aggregate Python Lists</title><link>https://saeedesmaili.com/notes/til-quickly-filter-and-aggregate-python-lists/</link><description>&lt;p&gt;Today I came across this brilliant python library called &lt;a href="https://github.com/mkalioby/leopards" target="_blank"&gt;leopards&lt;/a&gt;
 which allows you to do some basic but frequently used filters and aggregations on python lists. I&amp;quot;ve always used &lt;a href="https://github.com/pandas-dev/pandas" target="_blank"&gt;pandas&lt;/a&gt;
 for any quick and adhoc work with large lists or CSV files, but leopards sounds a quick and performant alternative when you don&amp;quot;t need to do any fancy data analysis work.&lt;/p&gt;
&lt;p&gt;Leopards provides following filters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;eq&lt;/code&gt;: equals and this default filter&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gt&lt;/code&gt;: greater than.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gte&lt;/code&gt;: greater than or equal.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lt&lt;/code&gt;: less than&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lte&lt;/code&gt;: less than or equal&lt;/li&gt;
&lt;li&gt;&lt;code&gt;in&lt;/code&gt;: the value in a list of a tuple.
&lt;ul&gt;
&lt;li&gt;e.g. &lt;code&gt;age__in=[10,20,30]&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;contains&lt;/code&gt;: contains a substring as in the example.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;icontains&lt;/code&gt;: case-insensitive &lt;code&gt;contains&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;startswith&lt;/code&gt;: checks if a value starts with a query strings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;istartswith&lt;/code&gt;: case-insensitive &lt;code&gt;startswith&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;endswith&lt;/code&gt;: checks if a value ends with a query strings.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iendswith&lt;/code&gt;: case-insensitive &lt;code&gt;endswith&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;isnull&lt;/code&gt;: checks if the value matches any of NULL_VALUES which are &lt;code&gt;(&amp;quot;&amp;quot;, &amp;quot;.&amp;quot;, None, &amp;quot;None&amp;quot;, &amp;quot;null&amp;quot;, &amp;quot;NULL&amp;quot;)&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;e.g. &lt;code&gt;filter__isnull=True&lt;/code&gt; or &lt;code&gt;filter__isnull=False&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A quick example of its usage with filters:&lt;/p&gt;</description><author>Saeed Esmaili</author><pubDate>Thu, 19 Dec 2024 21:45:19 GMT</pubDate><guid isPermaLink="true">https://saeedesmaili.com/notes/til-quickly-filter-and-aggregate-python-lists/</guid></item><item><title>Summary: Go for the No!</title><link>https://www.chestergrant.com/summary-go-for-the-no</link><description>&lt;div class="posthaven-post-body"&gt;&lt;p&gt;&lt;/p&gt;&lt;center&gt;        &lt;div class="posthaven-gallery" id="posthaven_gallery[2184123]"&gt;
                  &lt;p class="posthaven-file posthaven-file-image posthaven-file-state-processed"&gt;
          &lt;img class="posthaven-gallery-image" src="https://phaven-prod.s3.amazonaws.com/files/image_part/asset/3262337/5ULEOVPo12qHdqwnDCqZMDiizi4/medium_cover.png" /&gt;
        &lt;/p&gt;

        &lt;/div&gt;
&lt;/center&gt;&lt;b&gt;1. “If you’re not succeeding fast enough, you’re probably not failing fast enough, and you can’t have one without the other. So, if you’re going to avoid one, you’re going to avoid both.”&lt;/b&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;2. ‘The salesperson never decides when the sale is over; the customer does.’ Then he looked me in the eye and said, ‘Eric, your fear of hearing the word ‘no’ is the only thing standing between you and greatness.’&lt;/p&gt;&lt;p&gt;3. “After the conversation with Harold I started really looking at what set successful people apart from the masses, and their willingness to fail was at the top of the list.&lt;/p&gt;&lt;p&gt;4. Successful people fail eagerly while failures avoid failing. The whole point of becoming willing to fail more is to become a success, so that one day you won’t be forced to look back on your life and say to yourself, ‘I’m a failure.’&lt;/p&gt;&lt;p&gt;5. A willingness to fail means a person will tolerate just enough failure to get what they need from life, and no more. A wantingness, on the other hand, means you’re not just tolerating the no’s in your life, you’re actually beginning to seek them.&lt;/p&gt;&lt;p&gt;6. What if we decided to make each no we received and every rejection we encountered something that empowers us? Instead of avoiding rejection, what if we made the decision to seek rejection? Instead of avoiding no or perhaps simply tolerating it, what if we went out of our way to actually go for no!”&lt;/p&gt;&lt;p&gt;&lt;b&gt;7. “Simple. Rather than setting goals for the number of yes’s you are planning to get each week, you set goals for the number of no’s you’re going to collect.”&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;b&gt;8. “Let’s use my friend Paul who is in network marketing, for example. If his goal is to get ten people per week to come to a meeting, and typically about five percent of the people he approaches are willing to attend, then his goal would be to get one hundred and ninety people to say ‘no thanks.’”&lt;/b&gt;&lt;/p&gt;&lt;p&gt;9. Believe it or not, research shows that eighty-five percent of all interactions between retail salespeople and shoppers end without the salesperson ever asking for a buying decision.&lt;/p&gt;&lt;p&gt;10. “Absolutely! Studies show that as many as 80% of all salespeople don’t make it through their first year for the simple reason that they failed to make enough calls. That’s it. Nothing else.”&lt;/p&gt;&lt;p&gt;11. “That reminds me of this telemarketing guru I heard about,” I interjected. “He recommends that phone solicitors ask customers in the first ten seconds if they have interest in hearing about the product they’re selling. If the customer says no, then they politely say thank you and move on rather than going through their entire pitch. As a result, they make ten times as many calls, but only invest their time making their complete sales pitch to prospects who have just qualified themselves.” “It’s the same basic premise,” Cheryl replied. “You know, when you mine for gold, you don’t really look for the gold, you remove the dirt. Selling and gold mining are very much alike. It’s the people who remove the most dirt, who work their way through the greatest number of no’s, who ultimately discover the greatest number of golden yes’s!”&lt;/p&gt;&lt;p&gt;12. “the best piece of advice I can offer is to learn that no doesn’t mean never, it means not yet. Statistically, research shows that forty-four percent of salespeople give up after one no. Twenty-two more give up after the second no. Fourteen percent more give up after the third no. Twelve more give up after the fourth no. What does that come to?”&lt;/p&gt;&lt;p&gt;1&lt;b&gt;3. “Correct. Ninety-two percent of all salespeople give up without asking for the sale a fifth time, but research also shows that sixty percent of all customers say no four times before they finally say yes. That means the quickest way to separate yourself from the rest of the pack is to get at least five no’s from everyone you try to sell to!” “And you track that?” I asked.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;14. “Because when I get someone to say no, I can immediately move to the next step which is to ask, ‘why?’ Let me think about it teaches me absolutely nothing, but if they say no and I follow up with why, now I’m on the verge of discovering what I need to do next to make the sale.”&lt;/p&gt;&lt;p&gt;15. “Well, I’d have to say my advice would be this… If you’re going to fail, fail big!” Kurt stated. “Elaborate on that,” Eric encouraged. “What I mean is simply this,” Kurt continued. “Common sense says that if you’re going to get a no from somebody, get it from the client who needs twenty copiers, not just two. Get rejected by the purchasing agent from the company that buys forty thousand gallons of cleaning solution every month, not just forty.&lt;/p&gt;&lt;p&gt;16. “Think about the numbers for a moment. If I call on one hundred accounts that each have the potential to lease two copiers from me, and my closing ratio is ten percent, then I end up with twenty units out the door, right? But by focusing on accounts with the potential to lease twenty copiers, I only needed to close one sale to get the same twenty units!”&lt;/p&gt;&lt;p&gt;17. “For starters, I’d say that a primary key to creating outrageous success is to understand the need to fail exponentially. After all, one person can only fail so fast. Great leaders help everyone in the organization understand the need to fail faster.&lt;/p&gt;&lt;p&gt;18. “Exponential failure requires someone in the organization to be an exponent, in other words an advocate or spokesman, of the failure concept and to champion it. But I can’t begin to tell you how many executives I’ve seen who reach the top and suddenly forget what got them there. They start trying to avoid failure, and when a leader is afraid to fail, everyone in the organization knows it. Not only do people sense it but they figure that if you’re afraid of something, there’s a good chance it’s something they should be afraid of, too.”&lt;/p&gt;&lt;p&gt;19. “The second is to reward people for their failures, not just their successes,” he responded. “Everyone runs over to congratulate people for their sales successes, but how often do we go out of our way to congratulate people for their failures?” “Virtually never,” I said.&lt;/p&gt;&lt;p&gt;20. Don’t get me wrong; I’m all in favor of rewarding people for their successes, but not to the exclusion of recognizing the people who’ve displayed a true willingness and wantingness to fail.&lt;/p&gt;&lt;p&gt;21. “At CopyQuest, we recognize the top ten salespeople with a ‘Producers Pin’ award,” Eric continued, “but we also recognize the ten salespeople in the organization with the greatest number of failed attempts with what we call the ‘Go for No!’ award. And, if I recall correctly, Cheryl and Kurt made both lists.”&lt;/p&gt;&lt;p&gt;22. “That’s because we’ve caught on to the concept that winning a ‘Go for No!’ award almost assures that you’ll end up with a Producers Pin. Or, put another way,” Kurt concluded, “Yes is the destination, No is how you get there!”&lt;/p&gt;&lt;/div&gt;</description><author>Chester Grant</author><pubDate>Thu, 19 Dec 2024 21:03:32 GMT</pubDate><guid isPermaLink="true">https://www.chestergrant.com/summary-go-for-the-no</guid></item><item><title>The Fall of Democracy is a Markov Process</title><link>https://maximumeffort.substack.com/p/the-fall-of-democracy-is-a-markov</link><description>Did you know that the Ancient Greek word "anacyclosis" translates to "Markov-chain Monte-Carlo"?</description><author>Maximum Effort, Minimum Reward</author><pubDate>Thu, 19 Dec 2024 18:12:25 GMT</pubDate><guid isPermaLink="true">https://maximumeffort.substack.com/p/the-fall-of-democracy-is-a-markov</guid></item><item><title>“The Godfather II” of Tech Book Sequels</title><link>https://www.jabperf.com/the-godfather-ii-of-performance-book-sequels/</link><description>&lt;p&gt;I&amp;#8217;m going to tell you something that you probably already know. . . sequels usually suck. Oh, they got me when they told me that Denzel Washington would be in Gladiator II, that interminable dud of a film. How about that pitiful money-grab The Hangover II? And the &amp;#8220;My Belly Laugh&amp;#8221; differential between Bad Santa [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://www.jabperf.com/the-godfather-ii-of-performance-book-sequels/"&gt;&amp;#8220;The Godfather II&amp;#8221; of Tech Book Sequels&lt;/a&gt; appeared first on &lt;a href="https://www.jabperf.com"&gt;JabPerf Corp&lt;/a&gt;.&lt;/p&gt;</description><author>JabPerf Corp</author><pubDate>Thu, 19 Dec 2024 17:59:42 GMT</pubDate><guid isPermaLink="true">https://www.jabperf.com/the-godfather-ii-of-performance-book-sequels/</guid></item><item><title>The Absurdity of Big Chess</title><link>https://siddhesh.substack.com/p/big-chess</link><description>Babies watching Shakespeare</description><author>Obvious Bicycle</author><pubDate>Thu, 19 Dec 2024 15:03:22 GMT</pubDate><guid isPermaLink="true">https://siddhesh.substack.com/p/big-chess</guid></item><item><title/><link>https://mikewarot.blogspot.com/2024/12/i-cant-get-over-feeling-that-im-wasting.html</link><description>&lt;p&gt;I can't get over the feeling that I'm wasting whatever portion of my life I've got left.&lt;/p&gt;&lt;p&gt;In this video - https://www.youtube.com/watch?v=4jgTCayWlwc&lt;/p&gt;&lt;p&gt;"A rant on personal engineering projects" by BPS.shorts&lt;/p&gt;&lt;p&gt;The author makes the point that when you're working commercially on part of a project, the organization always has a way to keep the project going, even if you fail. If the cost goes over, they can usually change the scope or budget to fit it in.&amp;nbsp; If the parts don't integrate well, they can buy a system already integrated, or even the company that has already done it.&lt;/p&gt;&lt;p&gt;However, on a personal project, if any part fails, the whole thing fails. The main hazard is simply not finishing the project, rather than doing any one stage optimally. I think there's a shit-ton of value in this analysis.&lt;/p&gt;&lt;p&gt;Good Quote from Pale Rose in the comments @ https://www.youtube.com/watch?v=4jgTCayWlwc&amp;amp;lc=Ugxjv3J38ajw82_bmtF4AaABAg&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp; *Personal projects have a unique requirement that you must optimize for: 'finishing the damn project'. Everything else is secondary*&lt;/p&gt;&lt;span&gt;&lt;a name="more"&gt;&lt;/a&gt;&lt;/span&gt;&lt;p&gt;I want to be a high-?? individual, someone who can get shit done.&amp;nbsp; High agency?&lt;/p&gt;&lt;p&gt;Yep... that's the term, from https://x.com/tferriss/status/1719035575305408551&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;During my podcast interview with @EricRWeinstein, Eric said “high-agency person” in passing, and I asked him to elaborate:&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;“When you’re told that something is impossible, is that the end of the conversation, or does that start a second dialogue in your mind, how to get around whoever it is that’s just told you that you can’t do something? So, how am I going to get past this bouncer who told me that I can’t come into this nightclub? How am I going to start a business when my credit is terrible and I have no experience?”"&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Mike's plan for becoming high-agency&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style="text-align: left;"&gt;&lt;li&gt;List the projects I have, maintain said list&lt;/li&gt;&lt;li&gt;Always be working to finish the things on the list, or pruning the list&lt;/li&gt;&lt;li&gt;Always be gathering resources, or using them, never just hording them&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&amp;nbsp; &amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description><author>--Mike--</author><pubDate>Thu, 19 Dec 2024 12:35:44 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2024/12/i-cant-get-over-feeling-that-im-wasting.html</guid></item><item><title>It's my Birthday, here... have a BitGrid</title><link>https://mikewarot.blogspot.com/2024/11/its-my-birthday-here-have-bitgrid.html</link><description>&lt;p&gt;If you've read about von Neuman, and wondered what it was like to bootstrap computing, and if there could be another way.... here's an interesting Birthday Gift from me to you... the BitGrid, and a writeup, on an HN thread&lt;/p&gt;https://news.ycombinator.com/item?id=42115107</description><author>--Mike--</author><pubDate>Thu, 19 Dec 2024 12:31:02 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2024/11/its-my-birthday-here-have-bitgrid.html</guid></item><item><title>Black Doves (Limited Series): A dovish review during a red Christmas</title><link>https://olshansky.info/tv/black_doves/</link><description>Olshansky's review of Black Doves (Limited Series)</description><author>🦉 olshansky 🦁</author><pubDate>Thu, 19 Dec 2024 11:47:17 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/black_doves/</guid></item><item><title>Pydantic Logfire for LLM and API Observability</title><link>https://saeedesmaili.com/notes/til-pydantic-logfire-for-observability/</link><description>&lt;p&gt;I&amp;rsquo;ve been using &lt;a href="https://sentry.io/" target="_blank"&gt;sentry&lt;/a&gt;
 for automatically logging the errors and exceptions of my python projects. A few months ago I needed to log some information if a specific condition is true in my side project&amp;rsquo;s backend, but I wasn&amp;rsquo;t able to do this with sentry. It apparently can only work when something fails, and you can&amp;rsquo;t capture log messages if there&amp;rsquo;s no failure or exception.&lt;/p&gt;
&lt;p&gt;I looked for an affordable and user friendly observability tool and settled on using &lt;a href="https://axiom.co/" target="_blank"&gt;axiom&lt;/a&gt;
. It has a generous 500GB ingestion on free tier plan, but you can only view the events for the past 30 days time period. So I&amp;rsquo;ve been exporting the logs every month into a csv file, since I want to be able to view the trend of some behaviours over time.&lt;/p&gt;</description><author>Saeed Esmaili</author><pubDate>Thu, 19 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://saeedesmaili.com/notes/til-pydantic-logfire-for-observability/</guid></item><item><title>ob-metapost</title><link>https://her.esy.fun/posts/0028-ob-metapost/index.html</link><description>&lt;article&gt;&lt;p&gt;When I searched how to generate pictures in org-mode on the Internet,
Metapost is surprisingly missing.&lt;/p&gt;&lt;p&gt;If you never tried Metapost, I think you should really take a look at
this gem.&lt;/p&gt;&lt;p&gt;This article will simply provide an example how I could create an
emacs package (not yet distributed) to use Metapost in my org-mode
documents.&lt;/p&gt;&lt;h1 id="the-script"&gt;The script&lt;/h1&gt;&lt;p&gt;One of the main issue with Metapost is that, like
&lt;math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;semantics&gt;&lt;mrow&gt;&lt;msup&gt;&lt;mi&gt;L&lt;/mi&gt;&lt;mi&gt;A&lt;/mi&gt;&lt;/msup&gt;&lt;msub&gt;&lt;mi&gt;T&lt;/mi&gt;&lt;mi&gt;E&lt;/mi&gt;&lt;/msub&gt;&lt;mi&gt;X&lt;/mi&gt;&lt;/mrow&gt;&lt;annotation encoding="application/x-tex"&gt;L^AT_EX&lt;/annotation&gt;&lt;/semantics&gt;&lt;/math&gt;,
it generates a bunch of temporary files and clutter your current
directory with them. There is also a notion that Metapost is done to
generate not a single, but multiple files.&lt;/p&gt;&lt;p&gt;For our use case, we just want a program to generate a single
image.&lt;/p&gt;&lt;p&gt;This is why I wrote a very simple &lt;code&gt;mp2png&lt;/code&gt; script. It is
inspired by &lt;code&gt;mp2png.sh&lt;/code&gt; I could find in &lt;a href="https://github.com/foretspaisibles/bsdowl"&gt;bsdowl&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;In short, this script move a Metapost single figure code into a
temporary directory. We prepare enclose the figure description into a
Metapost template. We perform the compilation in that temporary
directory.&lt;/p&gt;&lt;p&gt;If you remove all the ceremony this is simply:&lt;/p&gt;&lt;div class="sourceCode" id="cb1"&gt;&lt;pre class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-1" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Generate a temporary directory&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-2"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-2" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;tmpdir&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="fu"&gt;mktemp&lt;/span&gt; &lt;span class="at"&gt;-d&lt;/span&gt;&lt;span class="va"&gt;)&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-3"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-3" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Name the temporary metapost&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-4"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-4" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;tmpmp&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;${tmpdir}&lt;/span&gt;&lt;span class="st"&gt;/tmp.mp&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-5"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-5" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Provide a prelude&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-6"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-6" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;cat&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$tmpmp&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="op"&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-7"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-7" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;prologues:=3;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-8"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-8" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;outputtemplate:=&amp;quot;tmp-1.eps&amp;quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-9"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-9" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-10"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-10" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# copy the content of your metapost code&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-11"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-11" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;cat&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$src&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$tmpmp&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-12"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-12" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# close the metapost file&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-13"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-13" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;cat&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$tmpmp&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="op"&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-14"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-14" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;end.&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-15"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-15" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-16"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-16" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Move to the temporary directory&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-17"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-17" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;pushd&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$tmpdir&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="kw"&gt;||&lt;/span&gt; &lt;span class="ex"&gt;err&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;ERROR: pushd &lt;/span&gt;&lt;span class="dt"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;${tmpdir}&lt;/span&gt;&lt;span class="dt"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-18"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-18" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Compile the metapost file&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-19"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-19" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;mpost&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;tmp.mp&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-20"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-20" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Convert the EPS file to PNG&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-21"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-21" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;gm&lt;/span&gt; convert &lt;span class="at"&gt;-density&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$density&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;tmp-1.eps&amp;quot;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;tmp-1.png&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-22"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-22" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Return to the original directory&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb1-23"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb1-23" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;popd&lt;/span&gt; &lt;span class="kw"&gt;||&lt;/span&gt; &lt;span class="ex"&gt;err&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;ERROR: popd (tmpdir= &lt;/span&gt;&lt;span class="dt"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;${tmpdir}&lt;/span&gt;&lt;span class="dt"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="st"&gt;)&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With all the details this become a bit more complex because I would
like to be able to pass the code from &lt;code class="verbatim"&gt;stdin&lt;/code&gt; as this will ease to write the org babel
code. Here is the full script:&lt;/p&gt;&lt;div class="sourceCode" id="cb2"&gt;&lt;pre class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-1" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;#!/usr/bin/env zsh&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-2"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-2" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-3"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-3" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# dependencies:&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-4"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-4" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# - metapost&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-5"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-5" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# - graphicsmagick&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-6"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-6" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-7"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-7" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;set&lt;/span&gt; &lt;span class="at"&gt;-e&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-8"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-8" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-9"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-9" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;cmd&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="va"&gt;${0&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt;t&lt;span class="va"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-10"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-10" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;showhelp()&lt;/span&gt; &lt;span class="kw"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-11"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-11" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$cmd&lt;/span&gt;&lt;span class="st"&gt; SRC [DEST]&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-12"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-12" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-13"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-13" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;SRC could be any file if - is provided read from stdin&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-14"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-14" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;    if - is provided the default file name will be result.png&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-15"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-15" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;DEST could be any file if - is provided read from stdin&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-16"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-16" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-17"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-17" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-18"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-18" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;if&lt;/span&gt; &lt;span class="kw"&gt;((&lt;/span&gt; &lt;span class="va"&gt;$#&lt;/span&gt; &lt;span class="op"&gt;==&lt;/span&gt; &lt;span class="dv"&gt;0&lt;/span&gt; &lt;span class="kw"&gt;));&lt;/span&gt; &lt;span class="cf"&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-19"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-19" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="ex"&gt;showhelp&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-20"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-20" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-21"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-21" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-22"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-22" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;src&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$1&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-23"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-23" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;density&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;1200&lt;/span&gt;
&lt;span id="cb2-24"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-24" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;tmpdir&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="fu"&gt;mktemp&lt;/span&gt; &lt;span class="at"&gt;-d&lt;/span&gt;&lt;span class="va"&gt;)&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-25"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-25" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;if&lt;/span&gt; &lt;span class="kw"&gt;((&lt;/span&gt; &lt;span class="va"&gt;$#&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&lt;/span&gt; &lt;span class="dv"&gt;1&lt;/span&gt; &lt;span class="kw"&gt;));&lt;/span&gt; &lt;span class="cf"&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-26"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-26" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="va"&gt;maindst&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$2&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-27"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-27" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;elif&lt;/span&gt; &lt;span class="kw"&gt;[[&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$src&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="ot"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;-&amp;quot;&lt;/span&gt; &lt;span class="kw"&gt;]];&lt;/span&gt; &lt;span class="cf"&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-28"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-28" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="va"&gt;maindst&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;result.png&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-29"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-29" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-30"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-30" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="va"&gt;maindst&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;${src&lt;/span&gt;&lt;span class="op"&gt;%&lt;/span&gt;.&lt;span class="pp"&gt;*&lt;/span&gt;&lt;span class="va"&gt;}&lt;/span&gt;&lt;span class="st"&gt;.png&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-31"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-31" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-32"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-32" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-33"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-33" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;err()&lt;/span&gt; &lt;span class="kw"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-34"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-34" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="bu"&gt;local&lt;/span&gt; &lt;span class="va"&gt;msg&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$1&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-35"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-35" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$msg&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&amp;amp;&lt;/span&gt;&lt;span class="dv"&gt;2&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-36"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-36" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="bu"&gt;exit&lt;/span&gt; 1&lt;/span&gt;
&lt;span id="cb2-37"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-37" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-38"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-38" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-39"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-39" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;dst&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$maindst&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-40"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-40" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# i=0&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-41"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-41" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# while [[ -e $dst ]]; do&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-42"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-42" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;#     dst=&amp;quot;${maindst%.*}-$((++i)).png&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-43"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-43" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# done&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-44"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-44" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb2-45"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-45" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;tmpmp&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;${tmpdir}&lt;/span&gt;&lt;span class="st"&gt;/tmp.mp&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-46"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-46" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;cat&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$tmpmp&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="op"&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-47"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-47" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;prologues:=3;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-48"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-48" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;outputtemplate:=&amp;quot;tmp-1.eps&amp;quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-49"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-49" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;% TEX macro is short enough to be copied&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-50"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-50" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;string preverbatimtex_, postverbatimtex_;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-51"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-51" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;vardef TEXPRE text s =  preverbatimtex_ := s; enddef;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-52"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-52" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;vardef TEXPOST text s =  postverbatimtex_ := s; enddef;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-53"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-53" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;vardef TEX primary s =&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-54"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-54" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;  if known preverbatimtex_:&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-55"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-55" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;    write &amp;quot;verbatimtex &amp;quot;&amp;amp;preverbatimtex_&amp;amp;&amp;quot; etex&amp;quot; to &amp;quot;mptextmp.mp&amp;quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-56"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-56" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;  fi&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-57"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-57" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;  write &amp;quot;btex &amp;quot;&amp;amp;s&amp;amp;&amp;quot; etex&amp;quot; to &amp;quot;mptextmp.mp&amp;quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-58"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-58" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;  if known postverbatimtex_:&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-59"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-59" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;    write &amp;quot;verbatimtex &amp;quot;&amp;amp;postverbatimtex_&amp;amp;&amp;quot; etex&amp;quot; to &amp;quot;mptextmp.mp&amp;quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-60"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-60" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;  fi&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-61"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-61" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;  write EOF to &amp;quot;mptextmp.mp&amp;quot;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-62"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-62" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;  scantokens &amp;quot;input mptextmp&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-63"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-63" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;enddef;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-64"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-64" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;beginfig(1);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-65"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-65" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-66"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-66" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;if&lt;/span&gt; &lt;span class="kw"&gt;[[&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$src&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="ot"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;-&amp;quot;&lt;/span&gt; &lt;span class="kw"&gt;]];&lt;/span&gt; &lt;span class="cf"&gt;then&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-67"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-67" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="fu"&gt;cat&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$tmpmp&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-68"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-68" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-69"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-69" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="fu"&gt;cat&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$src&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$tmpmp&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-70"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-70" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;fi&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-71"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-71" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;cat&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$tmpmp&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="op"&gt;&amp;lt;&amp;lt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-72"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-72" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;endfig;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-73"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-73" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;end.&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-74"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-74" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;EOF&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-75"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-75" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;pushd&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$tmpdir&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="kw"&gt;||&lt;/span&gt; &lt;span class="ex"&gt;err&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;ERROR: pushd &lt;/span&gt;&lt;span class="dt"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;${tmpdir}&lt;/span&gt;&lt;span class="dt"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-76"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-76" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;mpost&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;tmp.mp&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-77"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-77" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;gm&lt;/span&gt; convert &lt;span class="at"&gt;-density&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$density&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;tmp-1.eps&amp;quot;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;tmp-1.png&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-78"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-78" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;popd&lt;/span&gt; &lt;span class="kw"&gt;||&lt;/span&gt; &lt;span class="ex"&gt;err&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;ERROR: popd (tmpdir= &lt;/span&gt;&lt;span class="dt"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;${tmpdir}&lt;/span&gt;&lt;span class="dt"&gt;\&amp;quot;&lt;/span&gt;&lt;span class="st"&gt;)&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-79"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-79" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;cp&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;${tmpdir}&lt;/span&gt;&lt;span class="st"&gt;/tmp-1.png&amp;quot;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$dst&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-80"&gt;&lt;a href="https://her.esy.fun/posts/0028-ob-metapost/index.html#cb2-80" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;File: &lt;/span&gt;&lt;span class="va"&gt;$dst&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;And now for the code in emacs, inspired by &lt;code&gt;ob-ditaa&lt;/code&gt;:&lt;/p&gt;&lt;pre class="elisp"&gt;&lt;code&gt;;;; ob-metapost.el --- Babel Functions for metapost        -*- lexical-binding: t; -*-

;; Copyright (C) 2024 Free Software Foundation, Inc.

;; Author: Yann Esposito
;; Keywords: literate programming, reproducible research
;; Homepage: https://orgmode.org

;; This file is part of GNU Emacs.

;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs.  If not, see &amp;lt;https://www.gnu.org/licenses/&amp;gt;.

;;; Commentary:

;; Org-Babel support for evaluating metapost source code.
;;
;; This differs from most standard languages in that
;;
;; 1) there is no such thing as a &amp;quot;session&amp;quot; in metapost
;;
;; 2) we are generally only going to return results of type &amp;quot;file&amp;quot;
;;
;; 3) we are adding the &amp;quot;file&amp;quot; and &amp;quot;cmdline&amp;quot; header arguments
;;
;; 4) there are no variables (at least for now)

;;; Code:
(require 'ob)
(require 'org-compat)

(defvar org-babel-default-header-args:metapost
  '((:results . &amp;quot;file&amp;quot;)
    (:exports . &amp;quot;results&amp;quot;))
  &amp;quot;Default arguments for evaluating a metapost source block.&amp;quot;)

(defcustom org-babel-metapost-cmd &amp;quot;mp2png&amp;quot;
  &amp;quot;Metapost to png command&amp;quot;
  :group 'org-babel
  :type 'string)

(defun org-babel-execute:metapost (body params)
  &amp;quot;Execute a block of Metapost code with org-babel.
This function is called by `org-babel-execute-src-block'.&amp;quot;
  (let* ((out-file (or (cdr (assq :file params))
               (error &amp;quot;Metapost code block requires :file header argument&amp;quot;)))
     (cmdline (cdr (assq :cmdline params)))
     (in-file (org-babel-temp-file &amp;quot;metapost-&amp;quot;))
     (cmd (concat org-babel-metapost-cmd
                      &amp;quot; &amp;quot; cmdline
              &amp;quot; &amp;quot; (org-babel-process-file-name in-file)
              &amp;quot; &amp;quot; (org-babel-process-file-name out-file))))
    (with-temp-file in-file (insert body))
    (message cmd)
    (shell-command cmd)
    nil)) ;; signal that output has already been written to file

(defun org-babel-prep-session:metapost (_session _params)
  &amp;quot;Return an error because metapost does not support sessions.&amp;quot;
  (error &amp;quot;Metapost does not support sessions&amp;quot;))

(provide 'ob-metapost)

;;; ob-metapost.el ends here
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;And that's it. That was a lot easier than I expected. And now, I can
generate image like this:&lt;/p&gt;&lt;pre class="metapost"&gt;&lt;code&gt;u := 0.2cm;
z0 = (0,0);
z1 = (sqrt(3)*cm,0);
z2 = (sqrt(3)*cm,1cm);
draw z0--z1--z2--cycle;
label.bot(btex $\sqrt{3}$ etex, 1/2[z0,z1]);
label.rt(btex 1 etex, 1/2[z1,z2]);
label.top(btex 2 etex, 1/2[z0,z2]);
z3 = z1 - (u,0);
z4 = z1 + (-u,u);
z5 = z1 + (0,u);
draw z3--z4--z5;
&lt;/code&gt;&lt;/pre&gt;&lt;figure alt="Triangle with LaTeX fonts" id="fig:triangle"&gt;&lt;img src="https://her.esy.fun/posts/0028-ob-metapost/triangle.png.webp" /&gt;&lt;figcaption&gt;Triangle with LaTeX fonts&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;or this:&lt;/p&gt;&lt;pre class="metapost"&gt;&lt;code&gt;numeric r, theta; r = sqrt 1/2; theta = 45;
vardef dragon(expr a, b) =
  if abs(a-b) &amp;gt; 8:
    save p; pair p;
    p = r[a, b] rotatedabout(a, theta);
    dragon(a, p) &amp;amp; reverse dragon(b, p)
  else:
    a .. b
  fi
enddef;
vardef rounded_corners expr p =
  save r, n; numeric r, n; r = 1/4; n = length p;
  subpath (0, 1-r) of p
  for t=1 upto n-1:
    .. subpath (t+r, t+1-r) of p
  endfor .. subpath (n-r, n) of p
enddef;
draw rounded_corners dragon(origin,240 right) withcolor (.6,.2,.7);
&lt;/code&gt;&lt;/pre&gt;&lt;figure alt="Dragon fractal" id="fig:dragon"&gt;&lt;img src="https://her.esy.fun/posts/0028-ob-metapost/dragon.png.webp" /&gt;&lt;figcaption&gt;Dragon fractal&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;I hope it will help people to find the resources to write org babel
integrations as well as help people use Metapost more.&lt;/p&gt;&lt;/article&gt;</description><author>her.esy.fun</author><pubDate>Thu, 19 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://her.esy.fun/posts/0028-ob-metapost/index.html</guid></item><item><title>On The Road in Iceland!</title><link>https://theroadchoseme.com/iceland-glacier-capped-volcano</link><description>It&amp;#8217;s been a long time since I posted a upate here on The Road Chose Me &amp;#8211; I&amp;#8217;ve been busy with YouTube videos, writing books, speaking at events, writing magazine articles, building a new&amp;#46;&amp;#46;&amp;#46;</description><author>The Road Chose Me</author><pubDate>Wed, 18 Dec 2024 22:29:36 GMT</pubDate><guid isPermaLink="true">https://theroadchoseme.com/iceland-glacier-capped-volcano</guid></item><item><title>How Many Hours Can You Code?</title><link>https://thecodist.com/how-many-hours-can-you-code/</link><description>&lt;p&gt;How many hours a day can you write code, and at what point does the quality of your work go down? Even more important is how many weeks and months of that max effort you can still be effective.&lt;/p&gt;&lt;p&gt;In my life, there have only been three periods where I&lt;/p&gt;</description><author>The Codist</author><pubDate>Wed, 18 Dec 2024 22:03:18 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/how-many-hours-can-you-code/</guid></item><item><title>We all need our own philosophy</title><link>https://bowendwelle.substack.com/p/drapas-shirt</link><description>How a dream of "Drepa's Shirt" illustrates my personal philosophy and mythology</description><author>An Ordinary Disaster</author><pubDate>Wed, 18 Dec 2024 21:53:48 GMT</pubDate><guid isPermaLink="true">https://bowendwelle.substack.com/p/drapas-shirt</guid></item><item><title>A Guide to Commodore PETs</title><link>https://www.masswerk.at/nowgobang/2024/guide-to-pets</link><description>The original line-up of Commodore PET computers at a glance.</description><author>mass:werk – Now Go Bang!</author><pubDate>Wed, 18 Dec 2024 19:40:00 GMT</pubDate><guid isPermaLink="true">https://www.masswerk.at/nowgobang/2024/guide-to-pets</guid></item><item><title>Massively Multiplayer Governance</title><link>https://blog.erlend.sh/massively-multiplayer-governance?pk_campaign=rss-feed</link><description/><author>Open Indie</author><pubDate>Wed, 18 Dec 2024 17:00:06 GMT</pubDate><guid isPermaLink="true">https://blog.erlend.sh/massively-multiplayer-governance?pk_campaign=rss-feed</guid></item><item><title>On Scooters as a class of vehicle/tool</title><link>https://josh.works/scooters</link><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;

&lt;p&gt;Often when I say “scooter”, especially in the united states, the person thinks of something different than what I mean. Here’s a recent post from the &lt;a href="https://www.instagram.com/sportiquescooters"&gt;Sportique Scooters&lt;/a&gt; instagram:&lt;/p&gt;

&lt;p&gt;&lt;img alt="sportique" src="/images/sportique_insta.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://www.instagram.com/p/DB44uZbsGhK/"&gt;source&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This&lt;/em&gt; is the kind of vehicle I’m talking about when I say “scooter”.&lt;/p&gt;

&lt;p&gt;I once had a vehicle just like this photo. The helmet I used (and still use) is not as charming as this helmet, unforunately, but it is a bit safer, and I want the best head protection I can get. &lt;sup id="fnref:helmet"&gt;&lt;a class="footnote" href="#fn:helmet" rel="footnote"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;There’s more to say about this kind of vehicle, and I’ve tried to, a few different ways. This page currently serves as “just” a jumping off point to other scooter-related resources.&lt;/p&gt;

&lt;h2 id="collections-of-writings-about-scooters"&gt;Collections of writings about scooters&lt;/h2&gt;

&lt;p&gt;See, it’s not really about scooters, per se. It’s the verb of the thing. Scooters are different than cars, but the only reason it matters at all is because scooter-ing is vastly different than using a car, or a bike, or a motorcycle.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="/scootering"&gt;https://josh.works/scootering&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.tiktok.com/@josh_exists"&gt;My tiktok&lt;/a&gt; has lots of footage from/about scooting, mostly of me experimenting with different aspects of showing trips from place to place, or how I might view a certain feature of american road design.&lt;/p&gt;

&lt;p&gt;I’ve happily and comfortably ridden scooters around countries where nearly every motor on a roadway is attached to a scooter, and the driving norms are sometimes generous. It can be so peaceful. traffic flows very smoothly and at an incredible number of vehicles-per-minute through relatively low-square-meter intersections, in terms of land allocated to the intersection.&lt;/p&gt;

&lt;h2 id="footnotes"&gt;Footnotes&lt;/h2&gt;

&lt;div class="footnotes"&gt;
  &lt;ol&gt;
    &lt;li id="fn:helmet"&gt;

      &lt;p&gt;I always wear a full-face helmet of the “modern” style. An integrated sun visor is ideal, along with a clear visor that can be completely closed.&lt;/p&gt;

      &lt;p&gt;If you see some of my scooting videos you’ll have a sense of it.&lt;/p&gt;

      &lt;p&gt;These kinds of helmets are the most protective, provide the most protection from wind and sunlight and noise.&lt;/p&gt;

      &lt;p&gt;I also wear a disposable painters mask. It’s a heavy-duty N95 “Odor respirator”. Scooting makes one quite aware of air quality issues. Obviously there is plenty of vehicle tailpipe emissions, but also tire rubber microplastics AND metal dust from brakes, so there’s a lot in the air I want to keep out of my lungs.&lt;/p&gt;

      &lt;p&gt;Here’s what the internet has to say about these kinds of masks.&lt;/p&gt;

      &lt;blockquote&gt;
        &lt;p&gt;An R95 Odor Respirator 8246 is essentially the same as an N95 respirator in terms of filtering 95% of airborne particles, but the key difference is that the R95 is designed to be somewhat resistant to oil-based particles, while a standard N95 is not, making the R95 a better choice for situations where oil mists or fumes might be present; both are NIOSH-approved respirators.&lt;/p&gt;
      &lt;/blockquote&gt;

      &lt;p&gt;fumes? brake dust? airborn bits of rubber? I’ll wear the best mask I can, please, and I’ll wrap my whole head in a bubble (inside my full-face helmet with a closed visor) of mostly un-moving air, with all the vents closed up, unless the air is super clean.&lt;/p&gt;

      &lt;p&gt;&lt;a href="https://www.amazon.com/3M-Safety-8247HA1-A-Workshop-Respirator/dp/B002Y4E196/ref=sr_1_2?crid=3CGDB9NZ7XHK0&amp;amp;dib=eyJ2IjoiMSJ9.D4bJbO7VhC_gdahADUUzn5vdyXLTJW_nCNmaGSrK6Svqdms6F2j50XJFBTXmR90pTE3Dz8IeT9fVn7RJ7mX-jScBPQp3WJkv-KV9Prrq2xTwRNo-5zOsWc-9nkQ73ih4FP1b_VDydDIz6zY0mIVMUOJbD3nwp2ayLFUP3gn3PD9ztGAotvrXEhzRSP06JoFZZmhuEP6OezSAT16-uBtEVtC5P8xmXGg7V1jepSKdjc8.0PWurAG0HwrB5liZS3UpwL7OeSoMYAeY4hyu14HA1hc&amp;amp;dib_tag=se&amp;amp;keywords=odor+blocking+r95&amp;amp;qid=1734560422&amp;amp;sprefix=oder+blocking+r95%2Caps%2C144&amp;amp;sr=8-2"&gt;Here’s the exact 10-pack I last got off of Amazon&lt;/a&gt;. Once you try a nice mask like this, it might be hard to go back. Once I started getting choosy about masks, I started wearing them &lt;em&gt;a lot&lt;/em&gt;. I appreciate that to smell a vehicle or be downwind of it while it’s changing its speed via brakes and thus increased rolling friction on the ground, or its changing its speed via the engine, and thus increasing the particulate shedding rate and tailpipe emissions, is to be sorta ‘bathed’ in a plume of emisssions. &lt;a class="reversefootnote" href="#fnref:helmet"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</description><author>Josh Thompson</author><pubDate>Wed, 18 Dec 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://josh.works/scooters</guid></item><item><title>Building an AI-Powered Startup Job Matcher</title><link>https://ananthmajumdar.substack.com/p/building-an-ai-powered-startup-job</link><description>Through Agentic AI solutions to a handcrafted workflow</description><author>Ananth's Reflections</author><pubDate>Wed, 18 Dec 2024 05:51:54 GMT</pubDate><guid isPermaLink="true">https://ananthmajumdar.substack.com/p/building-an-ai-powered-startup-job</guid></item><item><title>Lightweight Web Analytics with Liwan</title><link>https://blog.henrygressmann.de/2024/liwan/</link><description>&lt;blockquote&gt;
&lt;p&gt;This post explores some of this project's background and technical aspects. If you're more so interested in Liwan itself, you can check out the &lt;a href="https://demo.liwan.dev/p/liwan.dev"&gt;demo instance&lt;/a&gt; and the docs on &lt;a href="https://liwan.dev"&gt;liwan.dev&lt;/a&gt;. The source code available on &lt;a href="https://github.com/explodingcamera/liwan"&gt;GitHub&lt;/a&gt; under the AGPL-3.0 license.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This summer, I started working on a small tool for collecting various metrics on my websites and this blog to see what posts are popular and also just to have a better understanding of how people use my sites. It's grown into a very useful tool for me and I've recently released the first major version of it, so I thought I'd write a bit about it.&lt;/p&gt;

  &lt;figure class="center"&gt;
    
      &lt;img src="https://blog.henrygressmann.de/2024/liwan/dashboard.jpg" /&gt;
    

    
      &lt;figcaption class="center"&gt;The Liwan Dashboard for one of my websites.&lt;/figcaption&gt;
    
  &lt;/figure&gt;

&lt;p&gt;Over the last ~5 years, I've tried out probably ten different analytics platforms after bailing on Google Analytics due to privacy concerns, but nothing ticked all the boxes for me:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I wanted strong privacy guarantees&lt;/strong&gt;. First of all, because it should be the default across all websites, but also because of how (rightfully) painful GDPR compliance can be when you collect too much data. I don't need to know your IP address or your browsing habits. There's no real reason for this data to leave my server; even when anonymized, there's always a risk of it being misused.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It should be set and forget&lt;/strong&gt;. I love that self-contained, statically linked binaries are making a comeback due to being the default with "newer" languages such as Go and Rust, which is fantastic for self-hosted software. Around the time I started working on this project, DuckDB 1.0 was also announced and later released, which seemed like a perfect tool for this. I value a simple setup process more than the ability to hyper-scale prematurely and endlessly customize everything.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Truly Open Source&lt;/strong&gt;: One of my favorite tools I tried was &lt;a href="https://usefathom.com/"&gt;fathom analytics&lt;/a&gt;, which sadly (but understandably) switched to a completely closed source SaaS model. I want to 'own' the data, even if the user's data is eventually anonymized; it's hard to trust what's happening on a server I don't control. I really like &lt;a href="https://plausible.io/"&gt;plausible&lt;/a&gt;'s open core model but I wanted something a bit more lightweight and opinionated.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi Website&lt;/strong&gt;: As I mentioned, I want to aggregate data from 10+ different sites, so multi-website support also had to be great. One platform I experimented with early on was &lt;a href="https://www.goatcounter.com/"&gt;GoatCounter&lt;/a&gt;. While the UI is bare bones, it's also very lightweight and available with an embedded database. Sadly, it does not offer great multi-website support and is generally not as feature-rich as I would like.&lt;/p&gt;
&lt;p&gt;Setting out with those and some more goals in mind, I started building &lt;a href="https://liwan.dev"&gt;Liwan.dev&lt;/a&gt;, which, as often happens, had a lot of feature creep but now ended up in a place I'm very happy with.&lt;/p&gt;
&lt;h1 id="simple-software"&gt;Simple Software&lt;/h1&gt;
&lt;p&gt;I love abstractions. Not in the OOP or the JavaScript way but in how a robust, tested, and, most importantly, focused library or app feels to use. This is one of the reasons I am so drawn to software engineering in the first place: the ability always to go one level deeper and understand how things work.&lt;/p&gt;
&lt;p&gt;You can overplay this - see the NPM ecosystem (this is honestly a bit of a strawman, &lt;a href="https://en.wikipedia.org/wiki/Npm_left-pad_incident"&gt;left-pad&lt;/a&gt; was not that bad), but it is a nice contrast from the attention-seeking everything apps and magic frameworks. There is a hard-to-find balance here - I won't argue in favor of building on hundreds of layers of magic and abstractions even when ignoring the supply chain concerns - but I really enjoy the current balance in the Rust ecosystem. The language itself doesn't matter, though; I mainly use it because I enjoy working with it.&lt;/p&gt;
&lt;p&gt;Liwan is built on hundreds of open-source libraries; you can see the list for yourself on the &lt;a href="https://demo.liwan.dev/attributions"&gt;attributions page&lt;/a&gt; shipped with every copy of Liwan. However, this doesn't mean it's not lightweight. Even when using the 'modern' web stack, resource-heavy and slow websites don't have to follow automatically. The dashboard is built using &lt;a href="https://astro.build/"&gt;Astro&lt;/a&gt;, a web framework built around reducing unnecessary overhead.&lt;/p&gt;
&lt;p&gt;A big part of the complexity of similar projects often comes from overly customizable Graph libraries. To reduce the amount of code that needs to be sent to the user, I build custom Graph and Map components directly on &lt;a href="https://d3js.org/"&gt;d3&lt;/a&gt;, keeping the entire JavaScript bundle shipped to users below 250kb. This even includes a large number of different icons and the data for the world map, which uses an heavily optimized &lt;a href="https://github.com/topojson/topojson"&gt;topojson&lt;/a&gt; file I created with data from the &lt;a href="https://www.naturalearthdata.com/"&gt;natural earth project&lt;/a&gt;.&lt;/p&gt;

  &lt;figure class="center"&gt;
    
      &lt;img src="https://blog.henrygressmann.de/2024/liwan/pagespeed.jpg" /&gt;
    

    
      &lt;figcaption class="center"&gt;Liwan&amp;#x27;s PageSpeed Insights.&lt;/figcaption&gt;
    
  &lt;/figure&gt;

&lt;p&gt;On the backend side, the main contributors to the amount of code are the embedded databases, DuckDB for events, and SQLite for the user data and authentication. The dashboard is transformed into static HTML, CSS, and JS and bundled with the rest of the code, something I've recently started doing with some of my other projects as well. Producing a single, universal artifact simplifies the entire build process and packaging containers greatly (Something that could be pushed even further using &lt;a href="https://justine.lol/ape.html"&gt;Actually Portable Executable&lt;/a&gt;), but I also decided to package it up as a Docker container as well in case you prefer that. This container just contains the Liwan binary and nothing else thanks to the static linking.&lt;/p&gt;
&lt;p&gt;To maximize compatibility across different Linux distro (you might have had issues with glibc on alpine containers before if you've worked with docker imaged), Liwan is also statically linked with &lt;a href="https://musl.libc.org/"&gt;musl libc&lt;/a&gt; and uses &lt;a href="https://github.com/rustls/rustls"&gt;rustls&lt;/a&gt; instead of linking against OpenSSL.&lt;/p&gt;
&lt;p&gt;Simple software doesn't stop here, however. Liwan is also built to require only a minimal amount of configuration and settings from the user before it is ready to process events. You execute the binary, and Liwan is ready. Everything will be placed in the correct place according to XDG Base Directory Specification, and you can start sending events to it right away.&lt;/p&gt;
&lt;p&gt;Early on, I also decided to add an onboarding page for users to create their initial user account. This page can only be accessed from a URL printed to the console on the first startup, protecting you from accidentally exposing this screen to the public internet and removing the need for default passwords.&lt;/p&gt;
&lt;h1 id="open-source"&gt;Open Source&lt;/h1&gt;
&lt;p&gt;As a small side note on the open-source part, I've thought a lot about how I wanted to license this project. I ended up going with the AGPL-3.0, which I'm not 100% happy with but is the best compromise for now. My one big gripe with the AGPL-3.0 is the mostly undefined virality boundaries, which probably apply less in the EU (to remedy this a bit, the tracker script is also available under the MIT license). In other projects, I usually use the MIT + Apache 2.0 dual license that is so common in the Rust ecosystem. Still, I want flexibility to allow me to monetize Liwan more easily in the future. To have the possibility to relicense it later and not need to set up a CLA, all contributions also need to be provided under the MIT license as well, something I haven't seen in many other projects and seems like a good compromise for both sides.&lt;/p&gt;
&lt;p&gt;The main alternative I considered was the EUPL. I've only seen it used on very few projects, but it's a bit more permissive than the AGPL. The author of GoatCounter has some interesting thoughts on why he chose it on his &lt;a href="https://www.arp242.net/license.html"&gt;blog&lt;/a&gt;. I don't like the license's wording: It allows relicensing the work under entirely different terms, and it just feels like a mess to me. It tries to be an "Interoperable Copyleft" license and explicitly states it's compatible with the GPL 2 and 3, but the GPL itself is &lt;em&gt;probably&lt;/em&gt; incompatible with the EUPL (&lt;a href="https://www.gnu.org/licenses/license-list.html#EUPL-1.1"&gt;see gnu.org&lt;/a&gt;), &lt;em&gt;except&lt;/em&gt; if you do a weird two-step relicensing dance. But I've &lt;a href="https://interoperable-europe.ec.europa.eu/collection/eupl/discussion/how-does-fsf-considers-eupl"&gt;read in forums&lt;/a&gt; that it still keeps the terms of the EUPL, so relicensing doesn't actually weaken it? I don't know, it's just a mess.&lt;/p&gt;
&lt;h1 id="try-it-out"&gt;Try it out!&lt;/h1&gt;
&lt;p&gt;Liwan is not perfect. There are many things I could optimize more, starting supporting huge datasets - going into the tens of millions of events can slow down things considerably - but it fits my personal use case (nearly) perfectly and probably a lot of others too.&lt;/p&gt;
&lt;p&gt;Just grab the latest version and try it out for yourself:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #282a36; color: #f8f8f2;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #6272a4;"&gt;# Download the latest release
&lt;/span&gt;&lt;span&gt;curl&lt;/span&gt;&lt;span style="font-style: italic; color: #ffb86c;"&gt; -JLO &lt;/span&gt;&lt;span style="color: #f1fa8c;"&gt;'https://github.com/explodingcamera/liwan/releases/latest/download/liwan-x86_64-unknown-linux-musl.tar.gz'
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #6272a4;"&gt;# Ensure the ~/.local/bin directory exists (You might want to add it to your PATH)
&lt;/span&gt;&lt;span&gt;mkdir&lt;/span&gt;&lt;span style="font-style: italic; color: #ffb86c;"&gt; -p &lt;/span&gt;&lt;span&gt;~/.local/bin
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #6272a4;"&gt;# Extract the binary
&lt;/span&gt;&lt;span&gt;tar&lt;/span&gt;&lt;span style="font-style: italic; color: #ffb86c;"&gt; -xzf&lt;/span&gt;&lt;span&gt; liwan-x86_64-unknown-linux-musl.tar.gz&lt;/span&gt;&lt;span style="font-style: italic; color: #ffb86c;"&gt; -C &lt;/span&gt;&lt;span&gt;~/.local/bin liwan
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #6272a4;"&gt;# Make the binary executable
&lt;/span&gt;&lt;span&gt;chmod +x ~/.local/bin/liwan
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #6272a4;"&gt;# Run the binary
&lt;/span&gt;&lt;span&gt;liwan&lt;/span&gt;&lt;span style="font-style: italic; color: #ffb86c;"&gt; --help
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;</description><author>henry's blog</author><pubDate>Wed, 18 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.henrygressmann.de/2024/liwan/</guid></item><item><title>Moana 2</title><link>https://blog.yiningkarlli.com/2024/12/moana-2.html</link><description>&lt;div style="display: none;"&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;

&lt;div class="tableofcontents"&gt;
    &lt;div class="tableofcontents-row"&gt;
        &lt;div class="tableofcontents-column3"&gt;
            &lt;div class="tableofcontents-content"&gt;
                1. &lt;a href="/2024/12/moana-2.html#2024-12-18-work"&gt;Making Moana 2&lt;/a&gt;&lt;br /&gt;
                2. &lt;a href="/2024/12/moana-2.html#2024-12-18-frames"&gt;Frames from the Film&lt;/a&gt;&lt;br /&gt;
                3. &lt;a href="/2024/12/moana-2.html#2024-12-18-references"&gt;References&lt;/a&gt;&lt;br /&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;div class="tableofcontents-column3"&gt;
        &lt;/div&gt;
        &lt;div class="tableofcontents-column3"&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;

&lt;div id="2024-12-18-work"&gt;&lt;/div&gt;

&lt;p&gt;This fall marked the release of &lt;em&gt;Moana 2&lt;/em&gt;, Walt Disney Animation’s 63rd animated feature and 
the 10th feature film rendered entirely using Disney’s Hyperion Renderer.
&lt;em&gt;Moana 2&lt;/em&gt; brings us back to the beautiful world of Moana, but this time on a larger adventure with a larger canoe, a crew to join our heroine, bigger songs, and greater stakes.
The first &lt;em&gt;Moana&lt;/em&gt; was at the time of its release one of the most beautiful animated films ever made, and &lt;em&gt;Moana 2&lt;/em&gt; lives up to that visual legacy with frames that match or often surpass what we did in the original movie.
I got to join &lt;em&gt;Moana 2&lt;/em&gt; about two years ago and this film proved to be an incredibly interesting 
project!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_003.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_003.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While we’ve now used Disney’s Hyperion Renderer to make several sequels to previous Disney Animation films, &lt;em&gt;Moana 2&lt;/em&gt; is the first time we’ve used Hyperion to make a sequel to a previous film that also used Hyperion.
From a technical perspective, the time between &lt;em&gt;Moana&lt;/em&gt; and &lt;em&gt;Moana 2&lt;/em&gt; is filled with almost a decade of continual advancement in our rendering technology and in our wider production pipeline.
At the time that we made the first &lt;em&gt;Moana&lt;/em&gt;, Hyperion was only a few years old and we spent a lot of time on the first &lt;em&gt;Moana&lt;/em&gt; fleshing out various still-underdeveloped features and systems in the renderer.
Going into &lt;em&gt;Moana 2&lt;/em&gt;, Hyperion is now an extremely mature, extremely feature rich, battle-tested production renderer with which we can make essentially anything we can imagine. 
Almost every single feature and system in Hyperion today has seen enormous advancement and improvement over what we had on the first &lt;em&gt;Moana&lt;/em&gt;; many of these advancements were in fact driven by hard lessons that we learned on the first &lt;em&gt;Moana&lt;/em&gt;!
Compared with the first &lt;em&gt;Moana&lt;/em&gt;, here’s a short, very incomplete laundry list of improvements made over the past decade that we were able to leverage on &lt;em&gt;Moana 2&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;em&gt;Moana 2&lt;/em&gt; uses a completely new water rendering system that represents an enormous leap in both render-time efficiency and easier artist workflows compared with what we used on the first &lt;em&gt;Moana&lt;/em&gt;; more on this later in this post.&lt;/li&gt;
  &lt;li&gt;After the first &lt;em&gt;Moana&lt;/em&gt;, we completely rewrote Hyperion’s previous volume rendering subsystem &lt;a href="https://doi.org/10.1145/3084873.3084907"&gt;[Habel 2017]&lt;/a&gt; from scratch; the modern system is a state-of-the-art delta-tracking system that required us to make foundational research advancements in order to implement [&lt;a href="https://doi.org/10.1145/3072959.3073665"&gt;Kutz et al. 2017&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3450623.3464644"&gt;Huang et al. 2021&lt;/a&gt;].&lt;/li&gt;
  &lt;li&gt;Our traversal system was completely rewritten to better handle thread scalability and to incorporate a form of rebraiding to efficiently handle gigantic world-spanning geometry; this was inspired directly by problems we had rendering the huge ocean surfaces and huge islands in the first &lt;em&gt;Moana&lt;/em&gt; &lt;a href="https://doi.org/10.1145/3182159"&gt;[Burley et al. 2018]&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;On the original &lt;em&gt;Moana&lt;/em&gt;, ray self-intersection with things like Maui’s feathers presented a major challenge; &lt;em&gt;Moana 2&lt;/em&gt; is the first film using our latest ray self-intersection prevention system that notably does away with any form of ray bias values.&lt;/li&gt;
  &lt;li&gt;We introduced a limited form of photon mapping on the first &lt;em&gt;Moana&lt;/em&gt; that only worked between the sun and water surfaces &lt;a href="https://doi.org/10.1145/3182159"&gt;[Burley et al. 2018]&lt;/a&gt;.; &lt;em&gt;Moana 2&lt;/em&gt; uses an evolved version of our photon mapper that supports all of our light types, many or our standard lighting features, and even has advanced capabilities like a form of spectral dispersion.&lt;/li&gt;
  &lt;li&gt;We’ve made a number of advancements [&lt;a href="https://drive.google.com/file/d/1kFpp_7I8vH8LHsf1Si94pqMkHxwinMSU/view"&gt;Burley et al. 2017&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/2897839.2927433"&gt;Chiang et al. 2016&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3306307.3328172"&gt;Chiang at al. 2019&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3532836.3536240"&gt;Zeltner et al. 2022&lt;/a&gt;] to various elements of the Disney BSDF shading model.&lt;/li&gt;
  &lt;li&gt;Subsurface scattering on the first &lt;em&gt;Moana&lt;/em&gt; was done using normalized diffusion; since then we’ve moved all subsurface scattering to use a state-of-the-art brute force path tracing approach &lt;a href="https://doi.org/10.1145/2897839.2927433"&gt;[Chiang et al. 2016]&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Eyes on the first &lt;em&gt;Moana&lt;/em&gt; used our old ad-hoc eye shader; eyes on &lt;em&gt;Moana 2&lt;/em&gt; use our modern physically plausible eye shader that includes state-of-the-art iris caustics calculated using manifold next event estimation &lt;a href="https://doi.org/10.1145/3214745.3214751"&gt;[Chiang &amp;amp; Burley 2018]&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;The emissive mesh importance sampling system that we implemented on the first &lt;em&gt;Moana&lt;/em&gt; and our overall many-lights sampling system has seen many efficiency improvements &lt;a href="https://doi.org/10.1145/3665320.3670993"&gt;[Li et al. 2024]&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;Hyperion has gained many more powerful features granting artists an enormous degree of artistic control both in the renderer and post-render in compositing [&lt;a href="https://www.jcgt.org/published/0008/04/02/"&gt;Burley 2019&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3641233.3664321"&gt;Burley et al. 2024&lt;/a&gt;].&lt;/li&gt;
  &lt;li&gt;Since the first &lt;em&gt;Moana&lt;/em&gt;, Hyperion’s subdivision/tessellation system has gained an advanced fractured mesh system that makes many of the huge-scale effects in the first &lt;em&gt;Moana&lt;/em&gt; movie much easier for us to create today &lt;a href="https://doi.org/10.1145/3532836.3536262"&gt;[Burley &amp;amp; Rodriguez 2022]&lt;/a&gt;.&lt;/li&gt;
  &lt;li&gt;We’ve introduced path guiding into Hyperion to handle particularly difficult light transport cases [&lt;a href="https://doi.org/10.1111/cgf.13227"&gt;Müller et al. 2017&lt;/a&gt;, &lt;a href="https://cgg.mff.cuni.cz/~jaroslav/papers/2019-path-guiding-course/index.htm"&gt;Müller 2019&lt;/a&gt;].&lt;/li&gt;
  &lt;li&gt;The original &lt;em&gt;Moana&lt;/em&gt; used our somewhat ad-hoc first-generation denoiser, while &lt;em&gt;Moana 2&lt;/em&gt; uses our best-in-industry, Academy Award winning&lt;sup id="2024-12-18-moana-2-footnote-1-backlink"&gt;&lt;a href="#2024-12-18-moana-2-footnote-1"&gt;1&lt;/a&gt;&lt;/sup&gt; second-generation deep learning denoiser jointly developed by Disney Research Studios, Disney Animation, Pixar, and ILM [&lt;a href="https://doi.org/10.1145/3197517.3201388"&gt;Vogels et al. 2018&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3306307.3328150"&gt;Dahlberg et al. 2019&lt;/a&gt;].&lt;/li&gt;
  &lt;li&gt;Even Hyperion’s internal architecture has changed enormously; Hyperion originally was famous for being a batched wavefront renderer, but this has evolved significantly since then and continues to evolve.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are many many more changes to Hyperion that there simply isn’t room to list here.
To give you some sense of how far Hyperion has evolved between &lt;em&gt;Moana&lt;/em&gt; and &lt;em&gt;Moana 2&lt;/em&gt;: the Hyperion used on &lt;em&gt;Moana&lt;/em&gt; was internally versioned as Hyperion 3.x; the Hyperion used on &lt;em&gt;Moana 2&lt;/em&gt; is internally versioned as Hyperion 16.x, with each version number in between representing major changes.
In addition to improvements in Hyperion, our rendering team has also been working for the past few years on a next-generation interactive lighting system that extensively leverages hardware GPU ray tracing; &lt;em&gt;Moana 2&lt;/em&gt; saw the widest deployment yet of this system.
I can’t say much more on this topic yet, but we’ve started to publish bits and pieces of work from this project, such as how we’ve created a new realtime ray tracing GPU Ptex implementation &lt;a href="https://doi.org/10.1145/3721239.3734098"&gt;[Lee et al. 2025]&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Of course, there are also still parts of Hyperion that have more or less remained exactly the same as they were during the original &lt;em&gt;Moana&lt;/em&gt;; these parts of the renderer have stood the test of time and proven to be reliable foundational pieces of the Disney Animation filmmaking process.
A great example is the fur/hair shading model &lt;a href="https://doi.org/10.1111/cgf.12830"&gt;[Chiang et al. 2016]&lt;/a&gt; that was originally developed for &lt;em&gt;Zootopia&lt;/em&gt; and used on human characters for the first time in &lt;em&gt;Moana&lt;/em&gt;.
Even though our hair simulation continues to advance with every movie &lt;a href="https://doi.org/10.1145/3721239.3734105"&gt;[Kaur et al. 2025]&lt;/a&gt;, it turns out that the Chiang fur/hair model has been so reliable thaat we haven’t really had to change it since, and in fact it has since become a de-facto standard across the entire graphics industry!&lt;/p&gt;

&lt;p&gt;Outside of the rendering group, literally everything else about our entire studio production pipeline has changed as well; the first &lt;em&gt;Moana&lt;/em&gt; was made mostly on proprietary internal data formats, while &lt;em&gt;Moana 2&lt;/em&gt; was made using the latest iteration &lt;a href="https://doi.org/10.1145/3721241.3733995"&gt;[Zhuang 2025]&lt;/a&gt; of our cutting-edge modern USD pipeline [&lt;a href="https://doi.org/10.1145/3532836.3536236"&gt;Miller et al. 2022&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3587421.3595421"&gt;Vo et al. 2023&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3641233.3664335"&gt;Li et al. 2024&lt;/a&gt;].
The modern USD pipeline has granted our pipeline many amazing new capabilities and far more flexibility, to the point where it became possible to move our entire lighting workflow to a new DCC [&lt;a href="https://doi.org/10.1145/3744199.3744636"&gt;Endo et al. 2025&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3721239.3734106"&gt;Joseph and Butt 2025&lt;/a&gt;] for &lt;em&gt;Moana 2&lt;/em&gt; without needing to blow up the entire pipeline.
Our next-generation interactive lighting system is similarly made possible by our modern USD pipeline. 
The modern pipeline has also allowed us to continue to push the scale of our films ever larger, with the ever-growing complexity of our crowds [&lt;a href="https://doi.org/10.1145/3721239.3734106"&gt;Ros and Berriz 2025&lt;/a&gt;, &lt;a href="https://doi.org/10.1145/3721239.3734085"&gt;Ros and Sullivan 2025&lt;/a&gt;] being a particular standout.&lt;/p&gt;

&lt;p&gt;While I get to work on every one of our feature films and get to do fun and interesting things every time, &lt;em&gt;Moana 2&lt;/em&gt; is the most direct and deep I’ve worked on one of our films probably since the original &lt;em&gt;Moana&lt;/em&gt;.
There are two specific projects I worked on for &lt;em&gt;Moana 2&lt;/em&gt; that I am particularly proud of: a completely new water rendering system that is part of &lt;em&gt;Moana 2&lt;/em&gt;’s overall new water FX workflow, and the volume rendering work that was done for the storm battle in the movie’s third act.&lt;/p&gt;

&lt;p&gt;On the original &lt;em&gt;Moana&lt;/em&gt;, we had to develop a lot of custom water simulation and rendering technology because commercial tools at the time couldn’t quite handle what the movie required.
On the simulation side, the original &lt;em&gt;Moana&lt;/em&gt; required Disney Animation to invent new techniques such as the APIC (affine particle-in-cell) fluid simulation model &lt;a href="https://doi.org/10.1145/2766996"&gt;[Jiang et al. 2015]&lt;/a&gt; and the FAB (fluxed animated boundary) method for integrating procedural and simulated water dynamics &lt;a href="https://doi.org/10.1145/3072959.3073597"&gt;[Stomakhin and Selle 2017]&lt;/a&gt;.
Disney Animation’s general philosophy towards R&amp;amp;D is that we will develop and invent new methods when needed, but will then aim to publish our work with the goal of allowing anything useful we invent to find its way into the wider graphics field and industry; a great outcome is when our publications are adopted by the commercial tools and packages that we build on top of.
APIC and FAB were both published and have since become a part of the stock toolset in Houdini, which in turn allowed us to build more on top of Houdini’s built-in SOPs for &lt;em&gt;Moana 2&lt;/em&gt;’s water FX workflow.&lt;/p&gt;

&lt;p&gt;On the rendering side, the main challenge on the original &lt;em&gt;Moana&lt;/em&gt; for rendering water was the need to combine water surfaces from many different sources (procedural, manually animated, and simulated) into a single seamless surface that could be rendered with proper refraction, internal volumetric effects, caustics, and so on.
Our solution to combine different water surfaces on the original &lt;em&gt;Moana&lt;/em&gt; was to convert all input water elements into signed distance fields, composite all of the signed distance fields together into a single world-spanning levelset, and then mesh that levelset into a triangle mesh for ray intersection &lt;a href="https://doi.org/10.1145/3084363.3085067"&gt;[Palmer et al. 2017]&lt;/a&gt;.
While this process produced great visual results, running this entire world-spanning levelset compositing and meshing operation at renderer startup for each frame proved to be completely untenable due to how slow it made interaction for artists, so an extensive system for pre-caching ocean surfaces overnight to disk had to be built out.
All in all, the water rendering and caching system on the first &lt;em&gt;Moana&lt;/em&gt; required a dedicated team of over half a dozen developers and TDs to maintain, and setting up the levelset compositing system correctly proved to be challenging for artists.&lt;/p&gt;

&lt;p&gt;For &lt;em&gt;Moana 2&lt;/em&gt;, we decided to revisit water rendering with the goal of coming up with something much easier for artists to use, much faster to render, and much easier to maintain by a smaller group of engineers and TDs.
Over the course of about half a year, we completely replaced the old levelset compositing and meshing system with a new ray-intersection-time CSG system.
Our new system requires almost zero work for artists to set up, requires zero preprocessing time before renderer startup and zero on-disk caching, renders with negligible impact on ray tracing speed, and required zero dedicated TDs and only part of my time as an engineer to support once primary development was completed.
In addition to all of the above, the new system also allows for both better looking and more memory efficient water because the level of detail that water meshes have to exist at is no longer constrained by the resolution of a world-size meshed levelset, even with an adaptive levelset meshing.
I think this was a great example where by revisiting a world that we already knew how to make, we were given an opportunity to reevaluate what we learned on &lt;em&gt;Moana&lt;/em&gt; in order to come up with something better by every metric for &lt;em&gt;Moana 2&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;We knew that returning to the world of Moana was likely going to require a heavy lift from a volume rendering perspective.
With a mind towards this, we worked closely with Disney Research|Studios in Zürich to implement next-generation volume path guiding techniques in Hyperion &lt;a href="https://www.yiningkarlli.com/projects/pathguidingcourse2025.html"&gt;[Reichardt et al. 2025]&lt;/a&gt;, which wound up not seeing wide deployment this time but nonetheless proved to be a fun and interesting project from which we learned a lot.
We also realized that the third act’s storm battle was going to be incredibly challenging from both an FX and rendering perspective; creating the storm battle required FX to invent whole new techniques &lt;a href="https://doi.org/10.1145/3721239.3734101"&gt;[Rice 2025]&lt;/a&gt;!
My last few months on &lt;em&gt;Moana 2&lt;/em&gt; were spent helping get the storm battle sequences finished; one extremely unusual thing we wound up doing was providing custom builds of Hyperion with specific optimizations tailored to the specific requirements of the storm sequence, sometimes going as far as to provide specific builds and settings tailored on a per-shot basis.
Normally this is something any production rendering team tries to avoid if possible, but one of the benefits of having our own in-house team and our own in-house renderer is that we are still able to do this when the need arises.
From a personal perspective, being able to point at specific shots and say “I wrote code for that specific thing” is pretty neat!&lt;/p&gt;

&lt;p&gt;From both a story and a technical perspective, &lt;em&gt;Moana 2&lt;/em&gt; is everything we loved from &lt;em&gt;Moana&lt;/em&gt; brought back, plus a lot of fun, big, bold new stuff &lt;a href="https://doi.org/10.1145/3721239.3734100"&gt;[Bhatawadekar et al. 2025]&lt;/a&gt;.
Making &lt;em&gt;Moana 2&lt;/em&gt; both gave us new challenges to solve and allowed us to revisit and come up with better solutions to old challenges from &lt;em&gt;Moana&lt;/em&gt;.
I’m incredibly proud of the work that my teammates and I were able to do on &lt;em&gt;Moana 2&lt;/em&gt;; I’m sure we’ll have a lot more to share at SIGGRAPH 2025, and in the meantime I strongly encourage you to see &lt;em&gt;Moana 2&lt;/em&gt; on the biggest screen you can find!&lt;/p&gt;

&lt;div id="2024-12-18-frames"&gt;&lt;/div&gt;

&lt;p&gt;To give you a taste of how beautiful this film looks, here are some frames from &lt;em&gt;Moana 2&lt;/em&gt; from the Blu-ray, 100% created using Disney’s Hyperion Renderer by our amazing artists. 
These are presented in no particular order:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_001.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_001.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_002.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_002.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_004.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_004.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_005.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_005.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_006.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_006.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_007.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_007.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_008.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_008.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_009.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_009.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_010.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_010.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_011.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_011.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_012.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_012.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_013.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_013.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_014.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_014.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_015.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_015.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_016.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_016.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_017.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_017.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_018.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_018.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_019.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_019.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_020.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_020.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_021.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_021.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_022.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_022.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_023.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_023.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_052.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_052.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_038.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_038.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_090.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_090.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_100.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_100.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_079.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_079.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_027.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_027.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_036.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_036.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_042.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_042.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_086.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_086.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_051.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_051.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_037.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_037.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_084.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_084.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_024.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_024.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_078.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_078.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_047.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_047.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_034.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_034.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_032.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_032.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_058.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_058.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_053.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_053.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_089.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_089.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_102.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_102.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_080.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_080.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_073.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_073.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_054.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_054.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_028.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_028.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_075.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_075.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_050.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_050.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_072.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_072.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_055.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_055.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_031.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_031.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_098.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_098.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_060.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_060.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_094.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_094.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_093.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_093.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_096.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_096.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_059.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_059.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_068.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_068.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_095.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_095.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_025.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_025.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_039.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_039.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_033.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_033.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_035.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_035.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_064.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_064.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_043.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_043.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_040.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_040.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_030.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_030.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_057.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_057.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_048.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_048.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_092.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_092.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_045.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_045.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_071.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_071.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_088.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_088.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_044.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_044.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_065.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_065.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_085.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_085.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_087.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_087.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_041.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_041.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_091.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_091.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_099.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_099.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_066.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_066.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_046.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_046.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_029.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_029.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_026.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_026.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_101.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_101.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_069.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_069.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_062.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_062.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_067.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_067.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_056.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_056.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_074.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_074.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_049.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_049.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_077.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_077.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_081.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_081.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_063.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_063.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_070.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_070.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_076.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_076.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_083.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_083.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_082.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_082.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_061.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_061.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_103.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_103.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_097.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_097.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_105.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_105.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_106.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_106.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_104.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_104.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_107.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_107.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_108.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_108.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_109.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_109.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_110.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_110.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_111.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_111.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is the credits frame for the Hyperion team, along with several of the other teams that we work closely with to make rendering happen at Disney Animation. Specifically, the Lighting &amp;amp; Materials team develops our render translation pipeline and much of the artist-facing user interfaces in our lighting and shading tools, and the Interactive Visualization team is our sibling team that develops our in-house realtime rasterizer viewports.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_credits.jpg"&gt;&lt;img alt="" src="https://blog.yiningkarlli.com/content/images/2024/Dec/moana2/GARLAND_credits.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All images in this post are courtesy of and the property of Walt Disney Animation Studios.&lt;/p&gt;

&lt;div id="2024-12-18-references"&gt;&lt;/div&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;

&lt;p&gt;Sucheta Bhatawadekar, Behzad Mansoori-Dara, Adolph Lusinsky, and Rob Dressel. 2025. &lt;a href="https://doi.org/10.1145/3721239.3734100"&gt;The Cinematography of Songs in Disney’s “Moana 2”&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2025 Talks&lt;/em&gt;. Article 64.&lt;/p&gt;

&lt;p&gt;Brent Burley, David Adler, Matt Jen-Yuan Chiang, Ralf Habel, Patrick Kelly, Peter Kutz, Yining Karl Li, and Daniel Teece. 2017. &lt;a href="https://drive.google.com/file/d/1kFpp_7I8vH8LHsf1Si94pqMkHxwinMSU/view"&gt;Recent Advancements in Disney’s Hyperion Renderer&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2017 Course Notes: &lt;a href="https://jo.dreggn.org/path-tracing-in-production/2017/index.html"&gt;Path Tracing in Production Part 1&lt;/a&gt;&lt;/em&gt;. 26-34.&lt;/p&gt;

&lt;p&gt;Brent Burley, David Adler, Matt Jen-Yuan Chiang, Hank Driskill, Ralf Habel, Patrick Kelly, Peter Kutz, Yining Karl Li, and Daniel Teece. 2018. &lt;a href="https://doi.org/10.1145/3182159"&gt;The Design and Evolution of Disney’s Hyperion Renderer&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics&lt;/em&gt; 37, 3 (Jul. 2018), Article 33.&lt;/p&gt;

&lt;p&gt;Brent Burley. 2019. &lt;a href="https://www.jcgt.org/published/0008/04/02/"&gt;On Histogram-Preserving Blending for Randomized Texture Tiling&lt;/a&gt;. &lt;em&gt;Journal of Computer Graphics Techniques&lt;/em&gt; 8, 4 (Nov. 2019), 31-53.&lt;/p&gt;

&lt;p&gt;Brent Burley and Francisco Rodriguez. 2022. &lt;a href="https://doi.org/10.1145/3532836.3536262"&gt;Fracture-Aware Tessellation of Subdivision Surfaces&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2022 Talks&lt;/em&gt;. Article 10.&lt;/p&gt;

&lt;p&gt;Brent Burley, Brian Green, and Daniel Teece. 2024. &lt;a href="https://doi.org/10.1145/3641233.3664321"&gt;Dynamic Screen Space Textures for Coherent Stylization&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2024 Talks&lt;/em&gt;. Article 50.&lt;/p&gt;

&lt;p&gt;Matt Jen-Yuan Chiang, Benedikt Bitterli, Chuck Tappan, and Brent Burley. 2016. &lt;a href="https://doi.org/10.1111/cgf.12830"&gt;A Practical and Controllable Hair and Fur Model for Production Path Tracing&lt;/a&gt;. &lt;em&gt;Computer Graphics Forum (Proc. of Eurographics)&lt;/em&gt; 35, 2 (May 2016), 275-283.&lt;/p&gt;

&lt;p&gt;Matt Jen-Yuan Chiang, Peter Kutz, and Brent Burley. 2016. &lt;a href="https://doi.org/10.1145/2897839.2927433"&gt;Practical and Controllable Subsurface Scattering for Production Path Tracing&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2016 Talks&lt;/em&gt;. Article 49.&lt;/p&gt;

&lt;p&gt;Matt Jen-Yuan Chiang and Brent Burley. 2018. &lt;a href="https://doi.org/10.1145/3214745.3214751"&gt;Plausible Iris Caustics and Limbal Arc Rendering&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2018 Talks&lt;/em&gt;. Article 15.&lt;/p&gt;

&lt;p&gt;Matt Jen-Yuan Chiang, Yining Karl Li, and Brent Burley. 2019. &lt;a href="https://doi.org/10.1145/3306307.3328172"&gt;Taming the Shadow Terminator&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2019 Talks&lt;/em&gt;. Article 71.&lt;/p&gt;

&lt;p&gt;Henrik Dahlberg, David Adler, and Jeremy Newlin. 2019. &lt;a href="https://doi.org/10.1145/3306307.3328150"&gt;Machine-Learning Denoising in Feature Film Production&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2019 Talks&lt;/em&gt;. Article 21.&lt;/p&gt;

&lt;p&gt;Colvin Kenji Endo, Norman Moses Joseph, Alex Nijmeh, and Todd Scopio. 2025. &lt;a href="https://doi.org/10.1145/3744199.3744636"&gt;Prototype to Production: Building a Lighting Workflow in Houdini for Animation&lt;/a&gt;. In &lt;em&gt;Proc. of Digital Production Symposium (DigiPro 2025)&lt;/em&gt;. Article  3.&lt;/p&gt;

&lt;p&gt;Ralf Habel. 2017. &lt;a href="https://doi.org/10.1145/3084873.3084907"&gt;Volume Rendering in Hyperion&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2017 Course Notes: &lt;a href="https://graphics.pixar.com/library/ProductionVolumeRendering/index.html"&gt;Production Volume Rendering&lt;/a&gt;&lt;/em&gt;. 91-96.&lt;/p&gt;

&lt;p&gt;Wei-Feng Wayne Huang, Peter Kutz, Yining Karl Li, and Matt Jen-Yuan Chiang. 2021. &lt;a href="https://doi.org/10.1145/3450623.3464644"&gt;Unbiased Emission and Scattering Importance Sampling for Heterogeneous Volumes&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2021 Talks&lt;/em&gt;. Article 3.&lt;/p&gt;

&lt;p&gt;Chenfafu Jiang, Craig Schroeder, Andrew Selle, Joseph Teran, and Alexey Stomakhin. 2015. &lt;a href="https://doi.org/10.1145/2766996"&gt;The Affine Particle-in-Cell Method&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics (Proc. of SIGGRAPH)&lt;/em&gt; 34, 4 (Aug. 2015), Article 51.&lt;/p&gt;

&lt;p&gt;Norman Moses Joseph and Rehan Butt. 2025. &lt;a href="https://doi.org/10.1145/3721239.3734106"&gt;The Design Opportunities of Moving to Houdini for Lighting with the world of Animation&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2025 Talks&lt;/em&gt;. Article 52.&lt;/p&gt;

&lt;p&gt;Avneet Kaur, Hubert Leo, Nachiket Pujari, and Bret Bays. 2025. &lt;a href="https://doi.org/10.1145/3721239.3734105"&gt;Choreography of Hair and Cloth in Disney’s “Moana 2”&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2025 Talks&lt;/em&gt;. Article 13.&lt;/p&gt;

&lt;p&gt;Peter Kutz, Ralf Habel, Yining Karl Li, and Jan Novák. 2017. &lt;a href="https://doi.org/10.1145/3072959.3073665"&gt;Spectral and Decomposition Tracking for Rendering Heterogeneous Volumes&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics (Proc. of SIGGRAPH)&lt;/em&gt; 36, 4 (Aug. 2017), Article 111.&lt;/p&gt;

&lt;p&gt;Mark S. Lee, Nathan Zeichner, and Yining Karl Li. 2025. &lt;a href="https://doi.org/10.1145/3721239.3734098"&gt;A Texture Streaming Pipeline for Real-Time GPU Ray Tracing&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2025 Talks&lt;/em&gt;. Article 12.&lt;/p&gt;

&lt;p&gt;Harmony M. Li, George Rieckenberg, Neelima Karanam, Emily Vo, and Kelsey Hurley. 2024. &lt;a href="https://doi.org/10.1145/3641233.3664335"&gt;Optimizing Assets for Authoring and Consumption in USD&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2024 Talks&lt;/em&gt;. Article 30.&lt;/p&gt;

&lt;p&gt;Yining Karl Li, Charlotte Zhu, Gregory Nichols, Peter Kutz, Wei-Feng Wayne Huang, David Adler, Brent Burley, and Daniel Teece. 2024. &lt;a href="https://doi.org/10.1145/3665320.3670993"&gt;Cache Points for Production-Scale Occlusion-Aware Many-Lights Sampling and Volumetric Scattering&lt;/a&gt;. In &lt;em&gt;Proc. of Digital Production Symposium (DigiPro 2024)&lt;/em&gt;. Article 6.&lt;/p&gt;

&lt;p&gt;Tad Miller, Harmony M. Li, Neelima Karanam, Nadim Sinno, and Todd Scopio. 2022. &lt;a href="https://doi.org/10.1145/3532836.3536236"&gt;Making Encanto with USD: Rebuilding a Production Pipeline Working from Home&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2022 Talks&lt;/em&gt;. Article 12.&lt;/p&gt;

&lt;p&gt;Thomas Müller. 2019. &lt;a href="https://cgg.mff.cuni.cz/~jaroslav/papers/2019-path-guiding-course/index.htm"&gt;Practical Path Guiding in Production&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2019 Course Notes: &lt;a href="https://cgg.mff.cuni.cz/~jaroslav/papers/2019-path-guiding-course/index.htm"&gt;Path Guiding in Production&lt;/a&gt;&lt;/em&gt;. 37-50.&lt;/p&gt;

&lt;p&gt;Thomas Müller, Markus Gross, and Jan Novák. 2017. &lt;a href="https://doi.org/10.1111/cgf.13227"&gt;Practical Path Guiding for Efficient Light-Transport Simulation&lt;/a&gt;. &lt;em&gt;Computer Graphics Forum (Proc. of Eurographics Symposium on Rendering)&lt;/em&gt; 36, 4 (Jun. 2017), 91-100.&lt;/p&gt;

&lt;p&gt;Sean Palmer, Jonathan Garcia, Sara Drakeley, Patrick Kelly, and Ralf Habel. 2017. &lt;a href="https://doi.org/10.1145/3084363.3085067"&gt;The Ocean and Water Pipeline of Disney’s Moana&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2017 Talks&lt;/em&gt;. Article 29.&lt;/p&gt;

&lt;p&gt;Lea Reichardt, Brian Green, Yining Karl Li, and Marco Manzi. 2025. &lt;a href="https://www.yiningkarlli.com/projects/pathguidingcourse2025.html"&gt;Path Guiding Surfaces and Volumes in Disney’s Hyperion Renderer- A Case Study&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2025 Course Notes: &lt;a href="https://sherholz.github.io/siggraph2025-path-guiding-course/"&gt;Path Guiding in Production and Recent Advancements&lt;/a&gt;&lt;/em&gt;. 30-66.&lt;/p&gt;

&lt;p&gt;Jacob Rice. 2025. &lt;a href="https://doi.org/10.1145/3721239.3734101"&gt;Steerable Perlin Noise&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2025 Talks&lt;/em&gt;. Article 1.&lt;/p&gt;

&lt;p&gt;Alberto J Luceño Ros and Cecilia Berriz. 2025. &lt;a href="https://doi.org/10.1145/3721239.3734106"&gt;Creating the Mudskipper Pile in Disney’s “Moana 2”: A Slippery Problem Space&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2025 Talks&lt;/em&gt;. Article 63.&lt;/p&gt;

&lt;p&gt;Alberto J Luceño Ros and Jeff Sullivan. 2025. &lt;a href="https://doi.org/10.1145/3721239.3734085"&gt;The Art of Crowds Animation&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2025 Talks&lt;/em&gt;. Article 62.&lt;/p&gt;

&lt;p&gt;Alexey Stomakhin and Andy Selle. 2017. &lt;a href="https://doi.org/10.1145/3072959.3073597"&gt;Fluxed Animated Boundary Method&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics (Proc. of SIGGRAPH)&lt;/em&gt; 36, 4 (Aug. 2017), Article 68.&lt;/p&gt;

&lt;p&gt;Emily Vo, George Rieckenberg, and Ernest Petti. 2023. &lt;a href="https://doi.org/10.1145/3587421.3595421"&gt;Honing USD: Lessons Learned and Workflow Enhancements at Walt Disney Animation Studios&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2023 Talks&lt;/em&gt;. Article 13.&lt;/p&gt;

&lt;p&gt;Thijs Vogels, Fabrice Rousselle, Brian McWilliams, Gerhard Röthlin, Alex Harvill, David Adler, Mark Meyer, and Jan Novák. 2018. &lt;a href="https://doi.org/10.1145/3197517.3201388"&gt;Denoising with Kernel Prediction and Asymmetric Loss Functions&lt;/a&gt;. &lt;em&gt;ACM Transactions on Graphics (Proc. of SIGGRAPH)&lt;/em&gt; 37, 4 (Aug. 2018), Article 124.&lt;/p&gt;

&lt;p&gt;Tizian Zeltner, Brent Burley, and Matt Jen-Yuan Chiang. 2022. &lt;a href="https://doi.org/10.1145/3532836.3536240"&gt;Practical Multiple-Scattering Sheen Using Linearly Transformed Cosines&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2022 Talks&lt;/em&gt;. Article 7.&lt;/p&gt;

&lt;p&gt;Rikki Zhuang. 2025. &lt;a href="https://doi.org/10.1145/3721241.3733995"&gt;Transitioning to an Explicit Dependency USD Asset Resolver&lt;/a&gt;. In &lt;em&gt;ACM SIGGRAPH 2025 Course Notes: &lt;a href="https://doi.org/10.1145/3721241.3733995"&gt;USD in Production&lt;/a&gt;&lt;/em&gt;. 73-106.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id="footnotes"&gt;Footnotes&lt;/h2&gt;

&lt;p&gt;&lt;sup id="2024-12-18-moana-2-footnote-1"&gt;1&lt;/sup&gt; Our deep learning denoiser technology is one of the &lt;a href="https://press.oscars.org/news/14-achievements-be-honored-scientific-and-technical-awardsr"&gt;2025 Academy of Motion Picture Arts and Sciences Scientific and Engineering Award&lt;/a&gt; winners.
&lt;a href="#2024-12-18-moana-2-footnote-1-backlink"&gt;&lt;span class="material-symbols-outlined"&gt;keyboard_return&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;</description><author>Code &amp;amp; Visuals</author><pubDate>Wed, 18 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.yiningkarlli.com/2024/12/moana-2.html</guid></item><item><title>Sorry Spotify, I Wrapped It Myself</title><link>https://taylor.town/wrapped</link><description>I meticulously document my album ratings (in plaintext).</description><author>taylor.town</author><pubDate>Wed, 18 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/wrapped</guid></item><item><title>AI Hasn't Helped Most Engineers (yet)</title><link>https://olivergilan.com/ai-hasnt-helped-most-engineers/</link><description>Most of the productivity gains from AI and other DevTools of the past decade have not made their way to the engineers working on large, mature codebases. If we want to make our biggest companies more productive we need a better system of record for the engineering org.</description><author>Oliver Gilan</author><pubDate>Wed, 18 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olivergilan.com/ai-hasnt-helped-most-engineers/</guid></item><item><title>swyx in 2024 End of Year wraps</title><link>https://www.swyx.io/2024-eoy</link><description>&lt;p&gt;i was involved in 3 end of year-ish recaps today:&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Tue, 17 Dec 2024 20:03:35 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/2024-eoy</guid></item><item><title>An Evolved Universal Transformer Memory</title><link>https://heidenstedt.org/links/an-evolved-universal-transformer-memory/</link><description>&lt;p&gt;
      &lt;em&gt;Best viewed on the &lt;a href="https://heidenstedt.org/links/an-evolved-universal-transformer-memory/"&gt;original page&lt;/a&gt;, where extended functionality like the
    footnote helper is available.&lt;/em&gt;
    &lt;/p&gt;&lt;p&gt;I stumbled on &lt;a href="https://news.ycombinator.com/item?id=42411409"&gt;HN&lt;/a&gt; over &lt;a href="https://sakana.ai/namm/"&gt;this&lt;/a&gt; very interesting article about a new kind of context memory system that, is able to remove information that is &amp;ldquo;unhelpful or redundant details&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Thinking further, i think this would be super helpful for semantic search, that is currently not very performant due to the missing filters that extract importance. I have tried to counter this problem until now via summarization through small LLMs, but as one might guess turns out as not very precise and super expensive. There are other ideas one could post process text with LLMs but they are not very efficient either.&lt;/p&gt;
&lt;p&gt;Paper &lt;a href="https://arxiv.org/abs/2410.13166"&gt;https://arxiv.org/abs/2410.13166&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="tldr-by-gpt-4o"&gt;&lt;a href="https://heidenstedt.org/links/an-evolved-universal-transformer-memory/#tldr-by-gpt-4o"&gt;TLDR by GPT-4o&lt;/a&gt;&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;(the article is very good, you might to prefer to read it over this TLDR, but here is it anyway)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Sakana AI introduces &lt;strong&gt;Neural Attention Memory Models (NAMMs)&lt;/strong&gt;, a novel memory system for transformers inspired by human selective memory. NAMMs optimize how transformers store and retrieve information, enabling them to &lt;strong&gt;“remember” important tokens and “forget” redundant ones&lt;/strong&gt;, significantly improving efficiency and performance, particularly for long-context tasks.&lt;/p&gt;
&lt;h3 id="key-technical-highlights"&gt;&lt;a href="https://heidenstedt.org/links/an-evolved-universal-transformer-memory/#key-technical-highlights"&gt;Key Technical Highlights:&lt;/a&gt;&lt;/h3&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Evolutionary Optimization&lt;/strong&gt;: NAMMs use evolutionary algorithms to train a neural classifier that decides which tokens to keep or discard, bypassing non-differentiable challenges.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execution Steps&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Convert attention sequences into &lt;strong&gt;spectrograms&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Compress data using an &lt;strong&gt;exponential moving average (EMA)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Use a classifier to score and selectively &lt;strong&gt;prune tokens&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generalization&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;NAMMs can &lt;strong&gt;zero-shot transfer&lt;/strong&gt; to other transformers (e.g., vision, reinforcement learning) without retraining.&lt;/li&gt;
&lt;li&gt;They adapt to tasks differently—retaining global information in early layers and focusing on local details in later ones.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Tested on &lt;strong&gt;LongBench, InfiniteBench&lt;/strong&gt;, and their Japanese benchmark &lt;strong&gt;ChouBun&lt;/strong&gt;, NAMMs reduce memory usage and outperform prior hand-designed memory strategies (H₂O, L₂).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;NAMMs demonstrate &lt;strong&gt;cross-domain mastery&lt;/strong&gt; and efficiency gains across diverse tasks and input modalities, paving the way for future research into learning transformers directly atop evolved memory systems.&lt;/p&gt;</description><author>Mia Heidenstedt</author><pubDate>Tue, 17 Dec 2024 12:51:53 GMT</pubDate><guid isPermaLink="true">https://heidenstedt.org/links/an-evolved-universal-transformer-memory/</guid></item><item><title>Great Resources for Learning and Implementing Retrieval-Augmented Generation (RAG)</title><link>https://pankajpipada.com/posts/2024-12-17-rag-resources/</link><description>Few great and free resources to learn about RAG and its usage in Generative AI.</description><author>Scramblings</author><pubDate>Tue, 17 Dec 2024 12:30:00 GMT</pubDate><guid isPermaLink="true">https://pankajpipada.com/posts/2024-12-17-rag-resources/</guid></item><item><title>Install Go on Ubuntu 24.04</title><link>https://swag.industries/install-go-on-ubuntu-24-04/</link><description>Install Go on Ubuntu 24.04 natively using apt or snap.</description><author>Swag Industries</author><pubDate>Tue, 17 Dec 2024 11:08:39 GMT</pubDate><guid isPermaLink="true">https://swag.industries/install-go-on-ubuntu-24-04/</guid></item><item><title>Emotional Design Article by Donald Norman</title><link>https://pankajpipada.com/posts/2024-12-17-emotion-and-design/</link><description>Extracts from the Emotional design article</description><author>Scramblings</author><pubDate>Tue, 17 Dec 2024 05:00:00 GMT</pubDate><guid isPermaLink="true">https://pankajpipada.com/posts/2024-12-17-emotion-and-design/</guid></item><item><title>Pixi - Real World Usage</title><link>https://blog.londogard.com/posts/2024-12-17-pixi-real-usage/</link><description>&lt;p&gt;Managing dependencies and environments across multiple platforms can be a nightmare. That’s why I was thrilled to discover &lt;a href="pixi.sh/dev/"&gt;Pixi&lt;/a&gt;. I’ve previous talked about Pixi on LinkedIn/Twitter, but haven’t used it in any “serious” project until recently and so far it has worked exceptional!&lt;/p&gt;
&lt;p&gt;Imagine a tool that combines the speed and efficiency of &lt;a href="https://github.com/astral-sh/uv"&gt;&lt;em&gt;uv&lt;/em&gt;&lt;/a&gt; with the robust package management of &lt;a href="https://github.com/mamba-org/mamba"&gt;&lt;em&gt;mamba&lt;/em&gt;&lt;/a&gt;. That’s Pixi in a nutshell. Built from the expertise drawn from as the mamba creators and utilizing &lt;code&gt;uv&lt;/code&gt; for PyPi dependenciess, Pixi offers a streamlined, powerful way to manage Python environments. Compared to &lt;em&gt;mamba&lt;/em&gt;, &lt;em&gt;pixi&lt;/em&gt; takes things one step further as their PyPi-dependencies are tested with conda on top of the additional tools brought by pixi, such as &lt;em&gt;tasks&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Cherry on top? Pixi is lightning fast and enables multi-platform &amp;amp; multi-environment inside a single file where everything is synced together.&lt;/p&gt;
&lt;blockquote class="blockquote"&gt;
&lt;p&gt;Multi-platform, multi-environment means that we can sync dependencies between osx-arm64, linux-64, CUDA, CPU, … - a standout feature!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;section class="level2" id="pixi-docker-builds"&gt;
&lt;h2 class="anchored"&gt;Pixi Docker Builds&lt;/h2&gt;
&lt;p&gt;After solving your local environment in a easy yet producible manner the next step is to solve it for your cloud workloads - &lt;em&gt;containerization&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Containerization is an important part of a developers toolkit in the modern world. To run cloud workloads it’s very common to deploy as a container, in Data Science this is for everything like Training, Inference and Data Pipelines.&lt;/p&gt;
&lt;p&gt;With pixi it’s quite straight-forward and they provide ready-to-use images through the &lt;a href="https://github.com/prefix-dev/pixi-docker"&gt;pixi-docker&lt;/a&gt; registry. There’s multiple base-images, including CUDA, to get started - it can’t be any simpler!&lt;/p&gt;
&lt;section class="level3" id="pixi-sample-docker-builds"&gt;
&lt;h3 class="anchored"&gt;Pixi Sample Docker Builds&lt;/h3&gt;
&lt;p&gt;Simple starter:&lt;/p&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb1" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode bash code-with-copy"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;span class="ex" style="color: null; background-color: null; font-style: inherit;"&gt;docker&lt;/span&gt; pull ghcr.io/prefix-dev/pixi:latest&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Find the different tags on &lt;a href="https://github.com/prefix-dev/pixi-docker?tab=readme-ov-file#pulling-the-images"&gt;Pixi Docker tags page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Efficient Production build by using Docker Multi-Stage Build: &lt;a href="https://github.com/prefix-dev/pixi-docker?tab=readme-ov-file#usage-with-shell-hook"&gt;prefix-docker/shell-hook&lt;/a&gt;.&lt;/p&gt;
&lt;/section&gt;
&lt;section class="level3" id="pixi-docker-build-on-aws-sagemaker"&gt;
&lt;h3 class="anchored"&gt;Pixi Docker Build on AWS Sagemaker&lt;/h3&gt;
&lt;p&gt;Sagemaker can be quite challenging to work with. While deploying custom Docker builds is easiest using their own base image, this image is often bloated with unnecessary dependencies. Additionally, to run &lt;code&gt;@remote&lt;/code&gt; jobs on AWS, you need to include a &lt;code&gt;conda&lt;/code&gt; or &lt;code&gt;mamba&lt;/code&gt; environment - something that &lt;code&gt;pixi&lt;/code&gt; doesn’t inherently use.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;So, how do we integrate Pixi with Sagemaker?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Here’s a workaround to make them play nicely together:&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;&lt;strong&gt;Include &lt;code&gt;micromamba&lt;/code&gt;:&lt;/strong&gt; Add &lt;code&gt;micromamba&lt;/code&gt; (available on &lt;code&gt;conda-forge&lt;/code&gt;) as a dependency in your &lt;code&gt;pixi.toml&lt;/code&gt;. This will allow us to create a conda-like environment within our Pixi setup.
&lt;ul&gt;
&lt;li&gt;In the future this could be done using a simple shell script, which is a planned improvement in my own projects.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add &lt;code&gt;micromamba&lt;/code&gt; to &lt;code&gt;$PATH&lt;/code&gt;:&lt;/strong&gt; Ensure that the &lt;code&gt;micromamba&lt;/code&gt; executable installed by Pixi is added to your system’s &lt;code&gt;$PATH&lt;/code&gt;. This will make it accessible to Sagemaker.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Set Environment Variables:&lt;/strong&gt; Configure necessary environment variables like &lt;code&gt;CONDA_PREFIX&lt;/code&gt; to point to the appropriate location where &lt;code&gt;micromamba&lt;/code&gt; will manage your environment.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With these steps, you’re ready to run your Pixi-managed projects on Sagemaker!&lt;/p&gt;
&lt;p&gt;In my experiments, this approach significantly reduced the size of my CUDA images from around 12 GB down to 4.5 GB - a massive improvement in terms of storage and deployment speed!&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section class="level2" id="pixi-multi-platformenvironment"&gt;
&lt;h2 class="anchored"&gt;Pixi Multi-Platform/Environment&lt;/h2&gt;
&lt;p&gt;One of Pixi’s standout features is its seamless support for multi-platform and multi-environment projects. While I initially planned to delve deeper into this, prefix.dev recently published an excellent guide on the topic. I highly recommend checking out their documentation on &lt;a href="https://pixi.sh/dev/features/pytorch/#mixing-macos-and-cuda-with-pypi-dependencies"&gt;combining different OS’s and environments (CPU, CUDA) with PyTorch&lt;/a&gt; for a comprehensive overview.&lt;/p&gt;
&lt;section class="level3" id="some-personal-comments"&gt;
&lt;h3 class="anchored"&gt;Some Personal Comments&lt;/h3&gt;
&lt;p&gt;Personally I find this part of pixi one of the biggest strengths, especially how easy it is to work with! To build a docker image you simply follow the basic example above, opting for &lt;code&gt;--feature=cuda&lt;/code&gt;.&lt;br /&gt;
The part of keeping lock-files on everything, while allowing certain OS:es missing out on dependencies makes it very practical in real-world scenarios!&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section class="level2" id="pixi-build-slimmming"&gt;
&lt;h2 class="anchored"&gt;Pixi Build Slimmming&lt;/h2&gt;
&lt;p&gt;When containerizing your code, it’s crucial to keep builds slim. Here are a few tricks to help you minimize your Pixi-based Docker images:&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;&lt;strong&gt;Leverage &lt;code&gt;.dockerignore&lt;/code&gt;:&lt;/strong&gt; Create a &lt;code&gt;.dockerignore&lt;/code&gt; file to exclude unnecessary files and directories (e.g., &lt;code&gt;.git&lt;/code&gt;, &lt;code&gt;__pycache__&lt;/code&gt;, tests) from your Docker build context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimize Dependencies:&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Carefully consider each dependency and remove any that are not strictly required for production.&lt;/li&gt;
&lt;li&gt;Utilize multiple environments within your &lt;code&gt;pixi.toml&lt;/code&gt;, e.g.&amp;nbsp;&lt;code&gt;prod&lt;/code&gt; and &lt;code&gt;dev&lt;/code&gt; environments. This allows you to exclude dev-specific dependencies (test, lint, ..) from your production container.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Employ Multi-Stage Docker Builds:&lt;/strong&gt; Multi-stage builds reduces the image size. Use a build stage to install dependencies and compile your application, and then copy only the necessary artifacts to a smaller, leaner final image. The &lt;code&gt;pixi-docker&lt;/code&gt; project provides guidance on using &lt;a href="https://github.com/prefix-dev/pixi-docker?tab=readme-ov-file#usage-with-shell-hook"&gt;multi-stage builds with shell-hook&lt;/a&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
&lt;section class="level1" id="pixi-vs-uv"&gt;
&lt;h1&gt;Pixi vs uv&lt;/h1&gt;
&lt;p&gt;While &lt;em&gt;uv&lt;/em&gt; has gained significant traction in the Python community, I believe &lt;em&gt;Pixi&lt;/em&gt; offers a more compelling solution for my specific needs, especially when it comes to complex, real-world projects.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;tasks&lt;/code&gt; are awesome.&lt;/strong&gt; They might not be perfect but they’re great to me!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multi-platform and Multi-environment projects&lt;/strong&gt; (personal opinion) somehow ends up easier in Pixi
&lt;ul&gt;
&lt;li&gt;I really tried to embrace the &lt;code&gt;uv&lt;/code&gt; approach as I appreciate it as more lightweight. But Pixi is somehow “smoother”.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pixi has base-images with CUDA&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Both tools are easy to build from a raw base-image too, so it’s not a huge problem&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access to &lt;code&gt;conda&lt;/code&gt; packages&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;Some hate it, but I like getting pre-built binaries.&lt;/li&gt;
&lt;li&gt;It’s quite interesting to install shell tools via &lt;code&gt;conda&lt;/code&gt; for container deployment.&lt;/li&gt;
&lt;/ul&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Possible to work with other languages than Python&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;section class="level3" id="what-is-the-one-big-uv-pro"&gt;
&lt;h3 class="anchored"&gt;What is the one big &lt;code&gt;uv&lt;/code&gt; pro?&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://docs.astral.sh/uv/guides/scripts/#declaring-script-dependencies"&gt;UV’s Inline Script Dependencies&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I think this feature is really cool, &lt;del&gt;but as pixi utilize &lt;code&gt;uv&lt;/code&gt; you can use it in &lt;code&gt;pixi&lt;/code&gt; too! ;)&lt;/del&gt; but it’s quite easy to replicate in &lt;code&gt;pixi&lt;/code&gt; as well (including with &lt;code&gt;uv&lt;/code&gt;)! ;)&lt;/p&gt;
&lt;div class="callout callout-style-default callout-tip callout-titled"&gt;
&lt;div class="callout-header d-flex align-content-center"&gt;
&lt;div class="callout-icon-container"&gt;
&lt;i class="callout-icon"&gt;&lt;/i&gt;
&lt;/div&gt;
&lt;div class="callout-title-container flex-fill"&gt;
&lt;span class="screen-reader-only"&gt;Tip&lt;/span&gt;How to run in ‘inline script’ in Pixi
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="callout-body-container callout-body"&gt;
&lt;p&gt;Simply call &lt;code&gt;pixi exec uv run a.py&lt;/code&gt;. See the &lt;a href="https://pixi.sh/latest/reference/cli/#exec"&gt;docs (cli/#exec)&lt;/a&gt; where you’re able to also run shell-scripts with a &lt;a href="https://pixi.sh/latest/advanced/shebang/"&gt;shebang&lt;/a&gt;. This will actually install &lt;code&gt;uv&lt;/code&gt; in a temporary env, and then use that &lt;code&gt;uv&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A bonus of &lt;code&gt;exec&lt;/code&gt; is that if you instead use the “pixi-native” &lt;del&gt;&lt;code&gt;--scope&lt;/code&gt;&lt;/del&gt; &lt;code&gt;--spec&lt;/code&gt; it supports conda too, e.g.&amp;nbsp;&lt;code&gt;pixi exec -s polars -s altair python&lt;/code&gt; to run a temporary python venv with &lt;code&gt;polars&lt;/code&gt; &amp;amp; &lt;code&gt;altair&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Added this callout 2025-03-07 and updated 2025-03-10 based on feedback from &lt;em&gt;markusschlenker&lt;/em&gt;.&lt;br /&gt;
&lt;strong&gt;Edit 2:&lt;/strong&gt; I want to add that you can also easily add &lt;code&gt;uv&lt;/code&gt; as a global tool (like installing it), through &lt;code&gt;pixi global install uv&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb2" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb2-1"&gt;&lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# /// script&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-2"&gt;&lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# dependencies = [&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-3"&gt;&lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;#   "requests&amp;lt;3",&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-4"&gt;&lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;#   "rich",&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-5"&gt;&lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# ]&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-6"&gt;&lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# ///&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-7"&gt;&lt;/span&gt;
&lt;span id="cb2-8"&gt;&lt;span class="im" style="color: #00769E; background-color: null; font-style: inherit;"&gt;import&lt;/span&gt; requests&lt;/span&gt;
&lt;span id="cb2-9"&gt;&lt;span class="im" style="color: #00769E; background-color: null; font-style: inherit;"&gt;from&lt;/span&gt; rich.pretty &lt;span class="im" style="color: #00769E; background-color: null; font-style: inherit;"&gt;import&lt;/span&gt; pprint&lt;/span&gt;
&lt;span id="cb2-10"&gt;&lt;/span&gt;
&lt;span id="cb2-11"&gt;resp &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; requests.get(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"https://peps.python.org/api/peps.json"&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb2-12"&gt;data &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; resp.json()&lt;/span&gt;
&lt;span id="cb2-13"&gt;pprint([(k, v[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"title"&lt;/span&gt;]) &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;for&lt;/span&gt; k, v &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;in&lt;/span&gt; data.items()][:&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;10&lt;/span&gt;])&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section class="level1" id="outro"&gt;
&lt;h1&gt;Outro&lt;/h1&gt;
&lt;p&gt;If you’re a Python developer struggling with dependency management, environment inconsistencies, or cumbersome container builds, I urge you to give Pixi a try. It’s a powerful tool that has the potential to streamline your workflow and make you a happier developer. Pixi has certainly made a significant difference in mine!&lt;/p&gt;
&lt;p&gt;Thanks for this time, Hampus Londögård&lt;/p&gt;


&lt;/section&gt;</description><author>Londogard Blog</author><pubDate>Tue, 17 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.londogard.com/posts/2024-12-17-pixi-real-usage/</guid></item><item><title>Getting started with automated quality assurance</title><link>https://paperless.blog/getting-started-with-automated-quality-assurance</link><description>The bad news is that every discussion we’ve had about tabs vs spaces was a waste of time, and neither of us learned anything useful. The good news is that we won’t need to have that discussion ever again - there are tools which apply idiomatic indentation for pretty much any language under the sun. But the good news don’t stop there. That discussion about whether entries in a long lists should all be on a single line, broken into long lines, or broken into one item per line? There’s a tool for that. Newline at end of file? There’s a tool for that. How imports should be sorted and grouped? Detecting overly general catch statements, unused variables, or bad spelling? You guessed it. In this article I’ll go through why you might want to automate QA, and some practicalities of how to adopt automated QA tools, so that the team can concentrate on the actually important work.</description><author>Paperless</author><pubDate>Tue, 17 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://paperless.blog/getting-started-with-automated-quality-assurance</guid></item><item><title>B-CRATOS: Föredrag om hjärnstyrda handproteser med fingertoppskänsla, på UU Centrum för forskning om funktionshinder - Antekningar</title><link>https://liza.io/b-cratos-f%C3%B6redrag-om-hj%C3%A4rnstyrda-handproteser-med-fingertoppsk%C3%A4nsla-p%C3%A5-uu-centrum-f%C3%B6r-forskning-om-funktionshinder-antekningar/</link><description>&lt;p&gt;Idag deltog jag i en föreläsning vid Uppsala universitets Centrum för forskning om funktionshinder (CFF), om en revolutionerande typ av hjärnimplantat och protes som möjliggör tvåvägskommunikation mellan hjärnan och protesen.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Mon, 16 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/b-cratos-f%C3%B6redrag-om-hj%C3%A4rnstyrda-handproteser-med-fingertoppsk%C3%A4nsla-p%C3%A5-uu-centrum-f%C3%B6r-forskning-om-funktionshinder-antekningar/</guid></item><item><title>Is GDP the North Star?</title><link>https://www.aswathkrishnan.com/2024/12/is-gdp-north-star.html</link><description>&lt;div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEhnHXWsfvwpLEAH9Dz61sbGGb2T4ACRgEUWTLknw0CrgxzkzIbPH90JlDTdKuPfi8YaSffcqO9oBmLk06YXSW_y9BdEyRzS7-20C6XxxVKW3MlAKNWRpufnuLh5B4OIs8YDv4GjP8FcThg7l1NMvAsp4fQ0PAo2-tJfmsmTPYLE0mN-FIedU8bapcfZnUhm" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="350" src="https://blogger.googleusercontent.com/img/a/AVvXsEhnHXWsfvwpLEAH9Dz61sbGGb2T4ACRgEUWTLknw0CrgxzkzIbPH90JlDTdKuPfi8YaSffcqO9oBmLk06YXSW_y9BdEyRzS7-20C6XxxVKW3MlAKNWRpufnuLh5B4OIs8YDv4GjP8FcThg7l1NMvAsp4fQ0PAo2-tJfmsmTPYLE0mN-FIedU8bapcfZnUhm=w350-h350" width="350" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;I was debating with friends about whether GDP should be the North Star for a country.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;What is GDP and why it matters&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;GDP measures the total exchange of goods and services in a country, capturing the scale of economic activity and trade. It’s an effective shorthand for material prosperity. Historically, countries with high GDPs tend to offer better living standards, infrastructure, and public services. When economies grow, people generally gain access to better healthcare, education, and opportunities.&lt;br /&gt;&lt;br /&gt;At a personal level, I align with Kanye’s philosophy: “Having money isn’t everything, but not having it is.” This captures the duality of wealth: it’s foundational for security and opportunity but insufficient as the ultimate life goal. Without it, basic survival becomes a struggle. But once those needs are met, the returns diminish, and other values, like social connections, health, or fulfillment, emerge.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Limits of GDP&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;GDP has significant blind spots. It doesn’t measure inequality, environmental sustainability, or quality of life. A rising GDP could reflect booming industries, but it could also mask glaring inequities. For example, the U.S. has one of the highest GDPs globally, yet struggles with income disparity, a loneliness epidemic, a declining sense of community, and healthcare affordability. Is that truly prosperity?&lt;br /&gt;&lt;br /&gt;Economist Simon Kuznets, the father of GDP, cautioned that it wasn’t designed to measure societal well-being. For instance, healthcare spending adds to GDP—whether it’s driven by cutting-edge innovation or a chronic disease epidemic. Similarly, natural disasters or environmental degradation can spike GDP due to reconstruction efforts, yet these don’t reflect progress.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;The paradox of prosperity&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;More wealth often comes at the expense of what matters most. A high-GDP lifestyle may demand long hours, geographic mobility, or environmental exploitation. The paradox is that the relentless pursuit of GDP may diminish the very well-being it’s meant to enhance.&lt;br /&gt;&lt;br /&gt;There’s also a cultural layer. The GDP race fosters a consumerist mindset: more goods, more spending, more growth. This doesn’t necessarily translate into happiness or fulfillment. Many of us recognize this at an individual level yet default to GDP when assessing national success. Why?&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Examples of alternative priorities&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;Take Bhutan. This small Himalayan country introduced Gross National Happiness (GNH) as an alternative framework. GNH measures well-being through pillars like environmental sustainability, cultural preservation, and mental health. It’s a direct challenge to GDP as the primary barometer of success. However, Bhutan’s approach raises questions: Is it scalable in complex, diverse economies? Is happiness too subjective to measure consistently?&lt;br /&gt;&lt;br /&gt;Or consider Scandinavian countries, often ranked high on happiness indexes. Their success isn’t purely about GDP; it’s also about policies that prioritize work-life balance, universal healthcare, and strong social safety nets.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A better North Star?&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;What if we reimagined success? Measuring progress through metrics like health spans, happiness, community, opportunity, carbon footprint, or even volunteerism might lead to rich societies in more than just economic terms.&lt;br /&gt;&lt;br /&gt;GDP is a vital but incomplete metric. It captures one dimension of prosperity but ignores the complexity of human flourishing. Perhaps the real question isn’t whether GDP should be our North Star but how we expand our constellation of metrics to guide us toward more holistic progress.&lt;/div&gt;</description><author>Aswath Krishnan</author><pubDate>Mon, 16 Dec 2024 19:24:34 GMT</pubDate><guid isPermaLink="true">https://www.aswathkrishnan.com/2024/12/is-gdp-north-star.html</guid></item><item><title>2024 X-Mas Demo (Commodore PET)</title><link>https://www.masswerk.at/nowgobang/2024/pet-x-mas-demo</link><description>It's that time of the tear again...</description><author>mass:werk – Now Go Bang!</author><pubDate>Mon, 16 Dec 2024 14:50:00 GMT</pubDate><guid isPermaLink="true">https://www.masswerk.at/nowgobang/2024/pet-x-mas-demo</guid></item><item><title>Local-only gitignore</title><link>https://evilcookie.de/local-only-gitignore.html</link><description/><author>blog</author><pubDate>Mon, 16 Dec 2024 12:46:53 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/local-only-gitignore.html</guid></item><item><title>Embedding Lua in sqleibniz with Rust</title><link>https://xnacly.me/posts/2024/embed-lua-in-rust/</link><description>Improving the SQL DX one blog article at a time</description><author>xnacly - blog</author><pubDate>Mon, 16 Dec 2024 12:12:45 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2024/embed-lua-in-rust/</guid></item><item><title>IdentityServer In Docker Containers – Handle Logout (Part 4)</title><link>https://nestenius.se/net/identityserver-in-docker-containers-part-4/</link><description>&lt;p&gt;In this final post in this series, we’ll now resolve logout challenges you might run into with IdentityServer, ensure proper sign-out redirects, and summarize the key takeaways from the series. Let’s complete the setup and finalize our IdentityServer configuration! This blog has been broken up into four separate posts: IdentityServer in Docker Containers: Adding Containers [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/net/identityserver-in-docker-containers-part-4/"&gt;IdentityServer In Docker Containers – Handle Logout (Part 4)&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Mon, 16 Dec 2024 10:13:24 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/net/identityserver-in-docker-containers-part-4/</guid></item><item><title>Core Values Wizard</title><link>https://lagomor.ph/projects/values/</link><description>&lt;p&gt;This interactive wizard guides you through a structured introspective journey to discover your 5 most essential core values. Based on the classic values card sort exercise, it helps you explore 235+ values across 7 categories, then progressively refine your selections until you identify what matters most.&lt;/p&gt;
&lt;p&gt;Your progress is automatically saved in your browser, so you can take your time and return whenever you&amp;rsquo;re ready.&lt;/p&gt;
&lt;hr /&gt;




&lt;div class="cv-container" id="cv-app"&gt;
 
 &lt;div class="cv-stage active"&gt;
 &lt;div class="cv-start"&gt;
 &lt;h2&gt;Discover Your Core Values&lt;/h2&gt;
 &lt;p&gt;Core values are the fundamental beliefs that guide your decisions and behavior. This exercise helps you identify the 5 values most essential to who you are.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Mon, 16 Dec 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/projects/values/</guid></item><item><title>Why I choose Microdata</title><link>https://seirdy.one/notes/2024/12/14/why-i-choose-microdata/</link><description>&lt;p&gt;The four most popular ways to use RDF-based metadata on websites are RDFa-Core, RDFa-Lite, Microdata, and inline JSON-LD.&lt;/p&gt;&lt;p&gt;I can’t use RDFa-Lite because I need &lt;code&gt;rel&lt;/code&gt; HTML attributes. &lt;a href="https://www.ctrl.blog/entry/rdfa-link-attributes.html"&gt;&lt;code&gt;rel&lt;/code&gt; silently upgrades RDFa-Lite to RDFa-Core&lt;/a&gt;, which parses differently. I doubt all parsers upgrade correctly; some will try to parse RDFa-Core as RDFa-Lite. Conformant RDFa parsers upgrade RDFa-Lite pages to RDFa-Core despite many authors only being familiar with RDFa-Lite. I suppose resources like Schema.org and Google’s documentation only documenting RDFa-Lite markup worsens the confusion. &lt;ins cite="https://w3c.social/@csarven/113658498996285762"&gt;Update &lt;time&gt;2024-12-16&lt;/time&gt;: &lt;span&gt;&lt;span class="h-card vcard"&gt;&lt;a class="u-url url" href="https://csarven.ca/#i"&gt;&lt;span class="p-name fn n"&gt;&lt;span class="p-given-name given-name"&gt;Sarven&lt;/span&gt;&lt;span class="p-family-name family-name"&gt;Capadisli&lt;/span&gt;&lt;/span&gt;&lt;/a&gt;&lt;/span&gt; has &lt;a href="https://csarven.ca/#i"&gt;clarified on the Fediverse&lt;/a&gt; that this is the behavior of one faulty parser; &lt;code&gt;rel&lt;/code&gt; only triggers an upgrade when used with an RDFa namespace. I may re-evaluate RDFa.&lt;/span&gt;&lt;/ins&gt;&lt;/p&gt;&lt;p&gt;With RDFa split between two incompatible alternatives with a confusing upgrade mechanism, the alternatives are Microdata and JSON-LD. I use structured data extensively; JSON-LD would duplicate most of the page. Let’s use &lt;a href="https://seirdy.one//posts/2024/05/30/google-document-warehouse-api-docs-leak/"&gt;this relatively short article&lt;/a&gt; as an example. &lt;a href="https://github.com/scrapinghub/extruct"&gt;Exruct&lt;/a&gt; can convert the embedded Microdata into a massive JSON document featuring JSON-LD. &lt;a href="https://paste.sr.ht/~seirdy/7db88ad2405d4ab685130cd513cd9defafd9d2cf"&gt;Take a look at the JSON-LD and HTML side by side&lt;/a&gt;. Microdata attributes take a fraction of the footprint, encode the same information, and don’t require duplicating nearly the entire page.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Mon, 16 Dec 2024 07:30:06 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2024/12/14/why-i-choose-microdata/</guid></item><item><title>Stevie Wonder in Los Angeles</title><link>https://digitalnomadder.micro.blog/2024/12/15/stevie-wonder-in.html</link><description>&lt;p&gt;&lt;img alt="" src="https://cdn.uploads.micro.blog/79953/2024/086fb68648.jpg" /&gt;
I was talking to my coworker and he mentioned that he was going to see Stevie Wonder.&lt;/p&gt;
&lt;p&gt;Seeing Stevie Wonder was on my bucket list and this may be his last concert.  So I decided to do an overnight trip.&lt;/p&gt;
&lt;p&gt;The concert was pretty good.  Too many guest singers, too much talking and my view on the floor wasn’t the greatest.&lt;/p&gt;
&lt;p&gt;It was nice to meet my coworker in person.&lt;/p&gt;
&lt;p&gt;I stayed at the &lt;a href="https://www.marriott.com/en-us/hotels/laxav-ac-hotel-downtown-los-angeles/overview/"&gt;AC Hotel in Downtown Los Angeles&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The view was great. The hotel was in a prime location and there were restaurants, bars and live music.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://cdn.uploads.micro.blog/79953/2024/991d082b83.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The hotel was right in front of the venue  &lt;a href="https://www.cryptoarena.com"&gt;Crypto Arena&lt;/a&gt;&lt;/p&gt;</description><author>The Digital Nomad</author><pubDate>Mon, 16 Dec 2024 02:28:14 GMT</pubDate><guid isPermaLink="true">https://digitalnomadder.micro.blog/2024/12/15/stevie-wonder-in.html</guid></item><item><title>Query a list of dictionaries with DuckDB</title><link>https://ricardoanderegg.com/posts/duckdb-query-list-of-dictionaries-python-dict/</link><description>&lt;p&gt;How to query a list of Python dictionaries using DuckDB, without previously converting to another format.&lt;/p&gt;
&lt;p&gt;We can pass the list of dictionaries to the query directly and use &lt;a href="https://duckdb.org/docs/sql/query_syntax/unnest.html"&gt;unnest&lt;/a&gt; to convert to a list of rows. We can also expand the struct to multiple columns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: all the dictionaries must have the same keys.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #8f5902; font-style: italic;"&gt;# /// script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #8f5902; font-style: italic;"&gt;# requires-python = "&amp;gt;=3.12"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #8f5902; font-style: italic;"&gt;# dependencies = ["duckdb"]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #8f5902; font-style: italic;"&gt;# ///&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #204a87; font-weight: bold;"&gt;import&lt;/span&gt; &lt;span style="color: #000;"&gt;duckdb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;DATA&lt;/span&gt; &lt;span style="color: #ce5c00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #000; font-weight: bold;"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #000; font-weight: bold;"&gt;{&lt;/span&gt;&lt;span style="color: #4e9a06;"&gt;"foo"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #0000cf; font-weight: bold;"&gt;1&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #4e9a06;"&gt;"bar"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #4e9a06;"&gt;"some string"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #4e9a06;"&gt;"baz"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #0000cf; font-weight: bold;"&gt;1.23&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #000; font-weight: bold;"&gt;{&lt;/span&gt;&lt;span style="color: #4e9a06;"&gt;"foo"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #0000cf; font-weight: bold;"&gt;2&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #4e9a06;"&gt;"bar"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #4e9a06;"&gt;"some other string"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #4e9a06;"&gt;"baz"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #0000cf; font-weight: bold;"&gt;2.34&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt; &lt;span style="color: #000; font-weight: bold;"&gt;{&lt;/span&gt;&lt;span style="color: #4e9a06;"&gt;"foo"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #0000cf; font-weight: bold;"&gt;3&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #4e9a06;"&gt;"bar"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #4e9a06;"&gt;"yet another string"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #4e9a06;"&gt;"baz"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #0000cf; font-weight: bold;"&gt;3.45&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #8f5902; font-style: italic;"&gt;# Select as struct&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;rel&lt;/span&gt; &lt;span style="color: #ce5c00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #000;"&gt;duckdb&lt;/span&gt;&lt;span style="color: #ce5c00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;query&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #4e9a06;"&gt;"SELECT unnest($data) as x"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #000;"&gt;params&lt;/span&gt;&lt;span style="color: #ce5c00; font-weight: bold;"&gt;=&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;{&lt;/span&gt;&lt;span style="color: #4e9a06;"&gt;"data"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #000;"&gt;DATA&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #8f5902; font-style: italic;"&gt;# Expand struct to rows&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;rel&lt;/span&gt; &lt;span style="color: #ce5c00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #000;"&gt;duckdb&lt;/span&gt;&lt;span style="color: #ce5c00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;query&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #4e9a06;"&gt;"SELECT x.* FROM (SELECT unnest($data) as x)"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;,&lt;/span&gt; &lt;span style="color: #000;"&gt;params&lt;/span&gt;&lt;span style="color: #ce5c00; font-weight: bold;"&gt;=&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;{&lt;/span&gt;&lt;span style="color: #4e9a06;"&gt;"data"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;:&lt;/span&gt; &lt;span style="color: #000;"&gt;DATA&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;result&lt;/span&gt; &lt;span style="color: #ce5c00; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #000;"&gt;duckdb&lt;/span&gt;&lt;span style="color: #ce5c00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;query&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #4e9a06;"&gt;"SELECT * FROM rel WHERE foo &amp;gt; 1"&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #204a87;"&gt;print&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;(&lt;/span&gt;&lt;span style="color: #000;"&gt;result&lt;/span&gt;&lt;span style="color: #ce5c00; font-weight: bold;"&gt;.&lt;/span&gt;&lt;span style="color: #000;"&gt;fetchall&lt;/span&gt;&lt;span style="color: #000; font-weight: bold;"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #8f5902; font-style: italic;"&gt;# [(2, 'some other string', 2.34), (3, 'yet another string', 3.45)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s a longer script, including timings, and different approaches. I&amp;rsquo;m using &lt;code&gt;.fetchdf().to_markdown()&lt;/code&gt; to pretty-print the results:&lt;/p&gt;</description><author>Posts on rand[om]</author><pubDate>Mon, 16 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ricardoanderegg.com/posts/duckdb-query-list-of-dictionaries-python-dict/</guid></item><item><title>Estimating projects sells them short (and that's okay)</title><link>https://ntietz.com/blog/estimating-projects-short-sale/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Recently, I read a blog post about doing software project estimates.
It's a reasonable post with a reasonable method.
But it does what all estimates do: it sells projects short.&lt;/p&gt;
&lt;p&gt;I don't mean in the sense of underestimating a young promising project's potential, relegating it to an unfulfilling career pushing paperwork.
I mean in the sense of selling short on the stock market.&lt;/p&gt;
&lt;h1 id="what-s-a-short-sale"&gt;What's a short sale?&lt;/h1&gt;
&lt;p&gt;Most of the time when you trade stocks, you buy a stock and you sell it later on.
This is how we buy most things in life: we pay money to get them, then later we can use them or get rid of them.
This works as we imagine it for stocks&lt;sup class="footnote-reference" id="fr-algo-1"&gt;&lt;a href="https://ntietz.com/blog/estimating-projects-short-sale/#fn-algo"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Let's say you buy a share of a company for $10.
Later, you sell it.
In the absolute worst case, the stock is worth $0 when you get rid of it&lt;sup class="footnote-reference" id="fr-startups-1"&gt;&lt;a href="https://ntietz.com/blog/estimating-projects-short-sale/#fn-startups"&gt;[2]&lt;/a&gt;&lt;/sup&gt;, and you're out all the money you put in.
You've lost 100% of your investment.
But if it's worth $10 when you sell it, you've gotten your money back (less any fees), yay!
And maybe it went up to $20, then you made a return of 100%.
Or it went to $50, and you made a return of 400%.
The amount of money you can make is technically unlimited.&lt;/p&gt;
&lt;p&gt;Then you have what's called a short sale.
In these, you sell a share you don't have yet.
To do that, you borrow it from some other party and promise to give it back to them eventually (and compensate them for the loan, too).
Your hope is that the value of the stock will go &lt;em&gt;down&lt;/em&gt; over time, so you can buy back the stock you sold for less than you sold it for.&lt;/p&gt;
&lt;p&gt;With short sales, how much you can make has a cap.
If you sell short a share worth $10 today, then the most you can make is $10 if the stock goes all the way down to $0.
But what happens if it goes up?
If it goes up to $40, now you'll lose $30—three times your investment—when you close out your position.&lt;/p&gt;
&lt;p&gt;Short sales are often pretty dangerous!
There are ways to reduce the risk, but unlimited downside risk is a big deal.
Even with hedges, there's a much bigger potential downside than when you buy a stock the normal way.&lt;/p&gt;
&lt;h1 id="estimating-software-projects"&gt;Estimating software projects&lt;/h1&gt;
&lt;p&gt;Now, what about software project estimates?&lt;/p&gt;
&lt;p&gt;Let's pretend that you, dear reader, are an independent software developer.
You do software development for clients, and they pay you a project-based fee rather than an hourly rate.
A good fee is one that's low enough that it's worth it to your client to accept, but high enough that you'll make a profit on it.
To reach what fee that is, you have to figure out how long a job will take.
If your only cost is your own labor, then the minimum charge to make a profit is the hours the project will take multiplied by your hourly rate.&lt;/p&gt;
&lt;p&gt;Let's say you estimate a project by breaking down the project into small tasks, then you estimate how long each of &lt;em&gt;those&lt;/em&gt; will take (using your expert knowledge), and add it up.
How close do you think you'll be for the total project time?&lt;/p&gt;
&lt;p&gt;My guess is you'll be pretty far off, and that you're going to underestimate the total time you need.
Part of this is from tasks you'll forget, and the rest is from getting your estimates wrong.
That's expected—that's why they're estimates, after all.
And the tasks you get done faster than expected won't cancel out the ones that take longer, but we'll get to that.&lt;/p&gt;
&lt;p&gt;So how do we handle underestimating completion time?&lt;/p&gt;
&lt;p&gt;Some people take a simple approach and pad the entire estimate.
This is crude, but can be quite effective.
You can take the entire estimate and multiply it by something (three? five?) to guess how long it will take when you have some tasks that are slow or some tasks you forgot, they're handled in that slush.&lt;/p&gt;
&lt;p&gt;If it works for you, great, a lot of people use it.
I don't love this approach, though.
It feels a little bit like bending the truth, unless you're transparent about your padding.
It would be nice if we had a way to figure out the real completion time from this estimate.
And it would be even better if we could &lt;em&gt;explain&lt;/em&gt; that to our clients.&lt;/p&gt;
&lt;h1 id="uncertainty-and-short-selling"&gt;Uncertainty and short selling&lt;/h1&gt;
&lt;p&gt;When you estimate a task, you're saying it will take some amount of time to complete.
Let's say you estimate that a certain task can be done in 10 hours.
If you're right about that, great!
If you're wrong, and it's faster, then at most you're saving 10 hours from your total project timeline.&lt;/p&gt;
&lt;p&gt;But if you're wrong and it takes longer...
Then it could take 20 hours, or 30, or 40.
Sometimes you can cut this out of scope, but sometimes this happens on a critical feature that you didn't expect (and sometimes scope gets added).&lt;/p&gt;
&lt;p&gt;Just as with short selling, the maximum you can be wrong by is unlimited.&lt;/p&gt;
&lt;p&gt;I worked on &lt;a href="https://remesh.blog/refactor-vs-rewrite-7b260e80277a"&gt;a project&lt;/a&gt; once where we got (to my recollection) within a few weeks of a correct estimate.
The project was on track for about four months, with about six engineers on it.
We eventually deviated from the plan because we got new information, so we changed course.
But that was quite a feat, being within &lt;em&gt;weeks&lt;/em&gt; on a four month chunk of the project.&lt;/p&gt;
&lt;p&gt;We did that by taking the uncertainty of each estimate into account.
Some tasks, we were quite certain, so we'd put 70-90% certainty, depending on our confidence.
Others, we were very &lt;em&gt;unsure&lt;/em&gt; of how long things would take, so we could get as low as 10%.
Then, the question was: how do we use this uncertainty to get our aggregate estimate?&lt;/p&gt;
&lt;p&gt;It depends on the distribution of values.
The normal distribution, our familiar bell curve, is quite common—but it isn't our distribution here, since it can extend infinitely far in either direction.
If I recall correctly, we used a &lt;a href="https://en.wikipedia.org/wiki/Poisson_distribution"&gt;Poisson distribution&lt;/a&gt;, and it looked something like this.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Illustration of something close to a Poisson distribution" src="https://ntietz.com/images/distribution-1.png" title="Illustration of something close to a Poisson distribution" /&gt;&lt;/p&gt;
&lt;p&gt;The upshot here is that given this sort of distribution, we capture the property of unlimited potential downside with limited upside.
And this is really the case with estimates!&lt;/p&gt;
&lt;h1 id="this-is-okay"&gt;This is okay&lt;/h1&gt;
&lt;p&gt;So, knowing that we are short selling our projects, what do we do?&lt;/p&gt;
&lt;p&gt;Honestly, probably, we just continue doing it—but with our eyes wide open.&lt;/p&gt;
&lt;p&gt;If you do project-based pricing, you can use this knowledge to reduce or acknowledge the risk you're taking.
And maybe you can use it to lock in better estimates, using historical data to figure out the actual distribution of tasks timing so you can build a nice statistical model.&lt;/p&gt;
&lt;p&gt;On the other hand, if you (like me) are working in a team that's building products and doesn't do client work per se, this is still useful!
It can help you with locking in more predictable timelines, though it's &lt;em&gt;usually&lt;/em&gt; not worth the effort to actually calculate all of this out.
The most useful thing I've found from this, though, is just &lt;em&gt;communication&lt;/em&gt;: having this understanding of unexpected events is really helpful for explaining potential risks before starting on a project, and for when you do hit those road bumps.&lt;/p&gt;
&lt;p&gt;If you do use uncertainty in your estimates, I'd love to hear about how you do it!&lt;/p&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-algo"&gt;
&lt;p&gt;As long as we imagine it being dominated by algorithmic trading and high-frequency trading.
We definitely should &lt;em&gt;not&lt;/em&gt; imagine humans yelling "buy! buy!" on the trading floor, though many prefer that you keep romanticizing the industry with that image. &lt;a href="https://ntietz.com/blog/estimating-projects-short-sale/#fr-algo-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-startups"&gt;
&lt;p&gt;This is what may happen if you exercise stock options in a startup, for example.
Many end up being worth $0, so if you exercise your options, you are likely to lose your entire investment. &lt;a href="https://ntietz.com/blog/estimating-projects-short-sale/#fr-startups-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 16 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/estimating-projects-short-sale/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Advent of Code... in SQL</title><link>/development/2024/12/16/advent-of-code-sql.html</link><description>&lt;p&gt;This year at work there’s been a push to brush up on relational database fundamentals. I’m generally pretty good at that, so I wanted to put my skills to the test. My preferred way to learn (or upskill) a new language in the month of December?&lt;/p&gt;

&lt;p&gt;&lt;a href="/puzzles/miscellaneous/2021/12/28/advent-of-code.html"&gt;Advent of Code!&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I’ve done AoC for the last 5 iterations starting in 2021, spanning a few languages – starting in Ruby, then Python, then Golang last year (but I got busy and didn’t finish), to finally a mix of Python and SQL. I get really sucked into these puzzles, so I wanted a challenge. So, I fired up PostgreSQL and figured I’d take a stab at writing some of the solutions in SQL.&lt;/p&gt;

&lt;h1 id="repository-link"&gt;Repository Link&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://github.com/ty-porter/advent-of-code/tree/master/2024/sql"&gt;https://github.com/ty-porter/advent-of-code/tree/master/2024/sql&lt;/a&gt;&lt;/p&gt;

&lt;h1 id="methodology"&gt;Methodology&lt;/h1&gt;

&lt;p&gt;I’m decent at writing SQL queries for business purposes but writing them to solve complicated problems like AoC would definitely prove to be quite difficult and potentially time-consuming. To save a little of my sanity, I chose to write the solutions out in Python before attempting them in SQL. Python for me is reasonably quick to prototype in, and crucially, solving it in Python gave me a foundation where I could check my work without fear of running into AoC’s wrong answer cooldown periods.&lt;/p&gt;

&lt;p&gt;Once I had a working solution in Python, the SQL scripts could be created.&lt;/p&gt;

&lt;h2 id="meta-tables-functions-batch-scripts"&gt;Meta-Tables, Functions, Batch Scripts&lt;/h2&gt;

&lt;p&gt;I created a few meta-scripts that did some basic stuff, like set up a &lt;code class="language-plaintext highlighter-rouge"&gt;solutions&lt;/code&gt; table for my answers and a &lt;code class="language-plaintext highlighter-rouge"&gt;raw_data&lt;/code&gt; table that would hold lines of the problem input. &lt;code class="language-plaintext highlighter-rouge"&gt;update_solution&lt;/code&gt;, a function to update a record in the &lt;code class="language-plaintext highlighter-rouge"&gt;solutions&lt;/code&gt; table.&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- common/setup.sql&lt;/span&gt;
&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;raw_data&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;position&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_data&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- There's a separate script to drop this, but it's not included here. It should persist between solutions.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;solutions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;day&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;

        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;update_solution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newday&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newpart&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newresult&lt;/span&gt; &lt;span class="n"&gt;ANYELEMENT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;VOID&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
        &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;solutions&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newday&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newpart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;newresult&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt;
        &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EXCLUDED&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;p&gt;Since I’m on Windows, then I defined a batch file that would seed the &lt;code class="language-plaintext highlighter-rouge"&gt;raw_data&lt;/code&gt; table via &lt;code class="language-plaintext highlighter-rouge"&gt;psql&lt;/code&gt; and run my solution scripts. There’s a whole host of scripts I used here, but this was the most common one:&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;# scripts/run.bat
@echo off

set dayno=%1
set connection=postgresql://aoc:aoc@localhost:5432/aoc

psql -c "SET client_min_messages TO NOTICE;" %connection%
psql --quiet -f .\common\setup.sql %connection%
psql --quiet -c "COPY raw_data (raw_data) FROM 'C:\\Users\tyler\development\advent-of-code\2024-sql\solutions\%dayno%\prompt.txt' WITH (FORMAT text)" %connection%
psql --quiet -f .\solutions\%dayno%\solution.sql %connection%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then I can run a script with &lt;code class="language-plaintext highlighter-rouge"&gt;scrips/run.bat &amp;lt;day&amp;gt;&lt;/code&gt;!&lt;/p&gt;

&lt;h2 id="solution-scripts"&gt;Solution Scripts&lt;/h2&gt;

&lt;p&gt;Now the fun can begin. Each solution script is designed to be idempotent – it builds up a schema that it will use for a solution, computes the result, writes it to the &lt;code class="language-plaintext highlighter-rouge"&gt;solutions&lt;/code&gt; table, and tears itself back down.&lt;/p&gt;

&lt;p&gt;First I parsed the raw data rows into some sort of useful table. If this is straightforward enough, an &lt;code class="language-plaintext highlighter-rouge"&gt;INSERT INTO ... SELECT&lt;/code&gt; was the preferred approach, but for more complex data I needed to use a &lt;code class="language-plaintext highlighter-rouge"&gt;PL/pgSQL&lt;/code&gt; function (by default I named it &lt;code class="language-plaintext highlighter-rouge"&gt;process_raw_data&lt;/code&gt;). For instance, if I needed to parse my raw data rows into a 2D grid, I might do something like:&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;DROP&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
        &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;process_raw_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;VOID&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
        &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="n"&gt;RECORD&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;char_row&lt;/span&gt; &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
        &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;raw_data&lt;/span&gt; &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="k"&gt;position&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
        &lt;span class="n"&gt;LOOP&lt;/span&gt;
                &lt;span class="n"&gt;char_row&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;string_to_array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;record&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
                &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="n"&gt;FOREACH&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="n"&gt;ARRAY&lt;/span&gt; &lt;span class="n"&gt;char_row&lt;/span&gt;
                &lt;span class="n"&gt;LOOP&lt;/span&gt;
                        &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;process_raw_data&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I honestly overused these functions. They were really only useful for looping over things, but in some cases looking back I can see that they either a) were just regular &lt;code class="language-plaintext highlighter-rouge"&gt;UPDATE&lt;/code&gt; / &lt;code class="language-plaintext highlighter-rouge"&gt;INSERT&lt;/code&gt; queries within the functions or b) could be converted to them easily.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;For the solution calculation itself, I liked using CTEs to break up the individual queries. It makes it clear what is an intermediate calculation vs. what the result is. Since there are two parts to a problem, I can potentially re-use those calculations with very little effort.&lt;/p&gt;

&lt;p&gt;My last few CTEs were usually called &lt;code class="language-plaintext highlighter-rouge"&gt;part1&lt;/code&gt;/&lt;code class="language-plaintext highlighter-rouge"&gt;part2&lt;/code&gt; and were simply a step to get the &lt;code class="language-plaintext highlighter-rouge"&gt;part&lt;/code&gt;/&lt;code class="language-plaintext highlighter-rouge"&gt;result&lt;/code&gt; column for the solutions table. After that, a &lt;code class="language-plaintext highlighter-rouge"&gt;UNION ALL&lt;/code&gt; on the two parts is readily inserted using my solution insert function.&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;subcalculation1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;-- Some expensive calculation&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subcalculation2&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="c1"&gt;-- Some other calculation&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;part1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;subcalculation1&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;part2&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;subcalculation2&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;update_solution&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;part1&lt;/span&gt;
        &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;part2&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id="so-howd-it-go"&gt;So How’d It Go?&lt;/h1&gt;

&lt;p&gt;Honestly, once you get used to it, SQL really isn’t that bad for doing AoC. I mean, it’s not the right tool for the job, but I expected it to be way worse. I thought it would be a certainty that I would need to stop around the end of the first week – at time of writing I’m at double that!&lt;/p&gt;

&lt;h2 id="the-good"&gt;The Good&lt;/h2&gt;

&lt;h3 id="leaning-into-set-theory"&gt;Leaning into set theory&lt;/h3&gt;

&lt;p&gt;Duh. That’s what SQL is made for.&lt;/p&gt;

&lt;p&gt;There’s lots of instances of this but let’s take &lt;a href="https://adventofcode.com/2024/day/7"&gt;Day 7&lt;/a&gt; as an example – we want to perform a list of operations on each row in a table. Well, we can do that, it’s a &lt;code class="language-plaintext highlighter-rouge"&gt;CROSS JOIN&lt;/code&gt; on the operands table that we can filter out.&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;bfs&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tgt&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;current_val&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operands&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;operands&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;array_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;tgtidx&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;[]::&lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;equations&lt;/span&gt; &lt;span class="n"&gt;eq&lt;/span&gt;

        &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tgt&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt;
                        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'+'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_val&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_val&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'|'&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="n"&gt;CONCAT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_val&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operands&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])::&lt;/span&gt;&lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;NUMERIC&lt;/span&gt;
                &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;current_val&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operands&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tgtidx&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;bfs&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
        &lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="k"&gt;unnest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARRAY&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'+'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'*'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'|'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;op&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;array_length&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operands&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;-- Use the result of this calculation...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id="storing-results"&gt;Storing results&lt;/h3&gt;

&lt;p&gt;Also duh.&lt;/p&gt;

&lt;p&gt;This is kind of obvious but when you’re working a complex puzzle like this, you have lots of options at your fingertips for data storage. A database after all is intended for long-term storage. You can use this to your advantage by leveraging CTEs, materialized views, temp tables, and even permanent working tables.&lt;/p&gt;

&lt;p&gt;In an imperative programming language, you would be reaching for something like a Python dict, list, or some other data structure to represent data. In Postgres, you really only have one option – a table (or a table-like structure). You really don’t have to think about it either, especially if using a CTE and can just pop a &lt;code class="language-plaintext highlighter-rouge"&gt;MATERIALIZED&lt;/code&gt; on there to “store” the results.&lt;/p&gt;

&lt;p&gt;The good thing about storage like this is it’s easy to make multiple passes of intermediate calculations on the same working data.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id="the-bad"&gt;The Bad&lt;/h2&gt;

&lt;h3 id="text-processing"&gt;Text processing&lt;/h3&gt;

&lt;p&gt;Getting useful data out of the raw data table was a pain almost every time. It can be done, but transforming the data was pretty wonky.&lt;/p&gt;

&lt;p&gt;Take a look at this from &lt;a href="https://github.com/ty-porter/advent-of-code/blob/c7cd30108b43e48e8c40265ec9eeace52e43880b/2024/sql/solutions/09/solution.sql#L10-L47"&gt;Day 9&lt;/a&gt;. It gets the job done, but is pretty messy. Most days relied on &lt;code class="language-plaintext highlighter-rouge"&gt;substr&lt;/code&gt; or some sort of regex function to parse the raw data rows. Other languages would process this in a more readable way.&lt;/p&gt;

&lt;h3 id="recursion--iteration"&gt;Recursion / iteration&lt;/h3&gt;

&lt;p&gt;Both of these are pretty rough. You &lt;em&gt;can&lt;/em&gt; do it, but it will not be enjoyable or particularly performant.&lt;/p&gt;

&lt;p&gt;I skipped &lt;a href="https://adventofcode.com/2024/day/6"&gt;Day 6&lt;/a&gt; Part 2 – the Part 1 solution I came up with iteratively created a path on a 2D grid. It ran in ~3s, but the Part 2 solution would have run that same code hundreds of times. It just wasn’t worth it.&lt;/p&gt;

&lt;p&gt;I had some luck with recursively generating a set of values like in &lt;a href="https://github.com/ty-porter/advent-of-code/blob/c7cd30108b43e48e8c40265ec9eeace52e43880b/2024/sql/solutions/08/solution.sql#L88-L104"&gt;Day 8&lt;/a&gt;, as long as the list of values was &lt;em&gt;very&lt;/em&gt; tightly bounded:&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;harmonics&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="o"&gt;*&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;antinodes&lt;/span&gt;

        &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dx&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dy&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;harmonics&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;
        &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;grid_bounds&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;
                &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;min_y&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
                &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_y&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;-- Do something with this calculation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I even got breadth-first searches working in a few cases (I overused BFS in my Python solutions, too):&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;bfs&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="n"&gt;id&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;ox&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;oy&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

        &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ox&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;oy&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;bfs&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
        &lt;span class="k"&gt;CROSS&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;directions&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
        &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;
                &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;
                &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;
                &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;part1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;DISTINCT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;oy&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;bfs&lt;/span&gt;
        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;-- Write out the result&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;More on BFS in SQL later.&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id="the-ugly"&gt;The Ugly&lt;/h2&gt;

&lt;p&gt;Also known as “&lt;strong&gt;how the hell did that even work??&lt;/strong&gt;”.&lt;/p&gt;

&lt;h3 id="every-time-you-need-a-piece-of-data-you-must-query-for-it"&gt;Every time you need a piece of data, you must query for it&lt;/h3&gt;

&lt;p&gt;Queries are &lt;strong&gt;expensive&lt;/strong&gt;. There’s a reason N + 1 is a huge problem for a database, as it racks up tons of IO / transit time rather than spending time finding things that actually matter. That means the programmer, especially a masochistic puzzle-solving programmer, is heavily incentivized to make as few queries in the hot loop as possible.&lt;/p&gt;

&lt;p&gt;Take for example &lt;a href="https://adventofcode.com/2024/day/12"&gt;Day 12&lt;/a&gt;, which is about identifying contiguous regions of letters and calculating their area (trivial), perimeter (fairly easy), and in Part 2, number of sides (now we’re talkin’).&lt;/p&gt;

&lt;p&gt;My solution needed all the neighbors for each cell. To do that in one query I aggregated each direction &lt;em&gt;in order&lt;/em&gt; using existence cast to &lt;code class="language-plaintext highlighter-rouge"&gt;'1'&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;'0'&lt;/code&gt; as the element. That gives a length 8 string that can be cast to a &lt;code class="language-plaintext highlighter-rouge"&gt;BIT(8)&lt;/code&gt; – an 8 bit number representing a bitmap whether a neighbor is present at a specified position. Can this be done better? I don’t know at this point, and I wasted hours trying to optimize it.&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;adjacent_cells&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="n"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="k"&gt;VALUES&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt;    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;-- Up&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt;    &lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;-- Right&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt;    &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;-- Down&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt;    &lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;-- Left&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;-- Up-Right&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;-- Down-Right&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;-- Down-Left&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;xi&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;yi&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;-- Up-Left&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;adjacent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ord&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ordered_cells&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; 
        &lt;span class="k"&gt;SELECT&lt;/span&gt;
                &lt;span class="n"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;
                &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt;
                        &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'1'&lt;/span&gt;
                        &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'0'&lt;/span&gt;
                &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;
        &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;adjacent_cells&lt;/span&gt; &lt;span class="n"&gt;ac&lt;/span&gt;
        &lt;span class="k"&gt;LEFT&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;
                &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="k"&gt;g&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt;
        &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;ac&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ord&lt;/span&gt; &lt;span class="k"&gt;ASC&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt;
        &lt;span class="n"&gt;string_agg&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;oc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;bitmask&lt;/span&gt;
&lt;span class="k"&gt;INTO&lt;/span&gt;
        &lt;span class="n"&gt;neighbors&lt;/span&gt; &lt;span class="c1"&gt;-- A variable to be used later&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;ordered_cells&lt;/span&gt; &lt;span class="n"&gt;oc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;hr /&gt;

&lt;h3 id="recursion--iteration-1"&gt;Recursion / iteration&lt;/h3&gt;

&lt;p&gt;I put this in the Bad section as it &lt;em&gt;does&lt;/em&gt; work when you get everything right, the approach suits the data, etc.&lt;/p&gt;

&lt;p&gt;When it goes wrong, oh boy are you out of luck.&lt;/p&gt;

&lt;p&gt;First of all the syntax is &lt;em&gt;really&lt;/em&gt; wonky, it requires a base case with a &lt;code class="language-plaintext highlighter-rouge"&gt;UNION ALL&lt;/code&gt; to the recursive portion:&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="k"&gt;RECURSIVE&lt;/span&gt; &lt;span class="n"&gt;subquery&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="c1"&gt;-- Base case&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;UNION&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt;

        &lt;span class="c1"&gt;-- Recursive case&lt;/span&gt;
        &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;subquery&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;subquery&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is a trivial example, but it’s quite simple to write a case that will simply never stop recursing. I ran into this many times, and because it isn’t in a function it’s not possible to &lt;code class="language-plaintext highlighter-rouge"&gt;RAISE NOTICE&lt;/code&gt; and effectively debug log your way out of the problem. Recursion specifically is not very performant, so if you need to check a lot of iterations, you’re better off creating your own functions.&lt;/p&gt;

&lt;p&gt;To go back to that &lt;a href="https://adventofcode.com/2024/day/12"&gt;Day 12&lt;/a&gt; example, I needed to write a custom BFS to define the regions. A recursive CTE simply timed out no matter what I tried (and I’m still not 100% sure what went wrong).&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;define_regions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="n"&gt;VOID&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
        &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;region_start&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;rows_updated&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
        &lt;span class="n"&gt;WHILE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;region_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;
                &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;region_start&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;region_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;
                &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;region_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt;
                &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;region_start&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="n"&gt;LOOP&lt;/span&gt;
                        &lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;adjacent&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                                &lt;span class="k"&gt;SELECT&lt;/span&gt;
                                        &lt;span class="n"&gt;adj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;
                                &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;
                                &lt;span class="k"&gt;INNER&lt;/span&gt; &lt;span class="k"&gt;JOIN&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="n"&gt;adj&lt;/span&gt;
                                        &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;
                                &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt;
                                        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;adj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region_id&lt;/span&gt; &lt;span class="k"&gt;IS&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;
                                        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;adj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;c&lt;/span&gt;
                                        &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;adj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;adj&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
                        &lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt;
                        &lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;region_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt;
                        &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;adjacent&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

                        &lt;span class="k"&gt;GET&lt;/span&gt; &lt;span class="k"&gt;DIAGNOSTICS&lt;/span&gt; &lt;span class="n"&gt;rows_updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ROW_COUNT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;rows_updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
                                &lt;span class="n"&gt;EXIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

                &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;LOOP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;define_regions&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Honestly, you should read &lt;a href="https://github.com/ty-porter/advent-of-code/blob/master/2024/sql/solutions/12/solution.sql"&gt;this entire solution&lt;/a&gt; for Day 12. Quite frankly I am in disbelief that it works at all – this seems like something Postgres is wholly unsuitable for:&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;# Breaking this:
AAAA
BBCD
BBCC
EEEC

# Into this:
+-+-+-+-+
|A A A A|
+-+-+-+-+     +-+
              |D|
+-+-+   +-+   +-+
|B B|   |C|
+   +   + +-+
|B B|   |C C|
+-+-+   +-+ +
          |C|
+-+-+-+   +-+
|E E E|
+-+-+-+

# Where:
#   A - area: 4, perimeter: 10, sides: 4
#   B - area: 4, perimeter:  8, sides: 4
#   C - area: 4, perimeter: 10, sides: 8
#   D - area: 1, perimeter:  4, sides: 4
#   E - area: 4, perimeter:  8, sides: 4
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id="the-syntax"&gt;The syntax&lt;/h3&gt;

&lt;p&gt;It’s ugly. I’m sorry. I know the math guys out there probably love it, but from a programming perspective it’s pretty clunky. Admittedly, puzzles make that worse – they are essentially adversarial inputs for what a database is trying to accomplish.&lt;/p&gt;

&lt;p&gt;On the other hand, getting the rows updated in a function should not look like this:&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;GET&lt;/span&gt; &lt;span class="k"&gt;DIAGNOSTICS&lt;/span&gt; &lt;span class="n"&gt;rows_updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;ROW_COUNT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="n"&gt;rows_updated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
        &lt;span class="n"&gt;EXIT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I shouldn’t have to write my own &lt;code class="language-plaintext highlighter-rouge"&gt;modulus&lt;/code&gt; function because Python uses a signed remainder definition for the &lt;code class="language-plaintext highlighter-rouge"&gt;%&lt;/code&gt; operator but Postgres uses a mathematical definition:&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- % and mod() return negative numbers for negative dividend / positive divisor and vice versa.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;REPLACE&lt;/span&gt; &lt;span class="k"&gt;FUNCTION&lt;/span&gt; &lt;span class="n"&gt;modulus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dividend&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;divisor&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURNS&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="err"&gt;$$&lt;/span&gt;
&lt;span class="k"&gt;DECLARE&lt;/span&gt;
        &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="nb"&gt;INT&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;BEGIN&lt;/span&gt;
        &lt;span class="n"&gt;divisor&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;divisor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;mod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dividend&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;divisor&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt;
                &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;divisor&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;END&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="k"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="err"&gt;$$&lt;/span&gt; &lt;span class="k"&gt;LANGUAGE&lt;/span&gt; &lt;span class="n"&gt;plpgsql&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s weird that casting from &lt;code class="language-plaintext highlighter-rouge"&gt;BIT(8)&lt;/code&gt; to &lt;code class="language-plaintext highlighter-rouge"&gt;BIT(4)&lt;/code&gt; preserves the highest bits:&lt;/p&gt;

&lt;div class="language-sql highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;aoc&lt;/span&gt;&lt;span class="o"&gt;=#&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="s1"&gt;'11110000'&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;BIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)::&lt;/span&gt;&lt;span class="nb"&gt;BIT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

 &lt;span class="nb"&gt;bit&lt;/span&gt;
&lt;span class="c1"&gt;------&lt;/span&gt;
 &lt;span class="mi"&gt;1111&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I’m sure there’s more.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;All in all, it was a lot of fun. As of time of writing (December 16), I made it to Day 14. I’m 2 days behind and not sure if I’ll make up the difference. Day 12 was a significant effort and Days 15 and 16 were of similar size.&lt;/p&gt;

&lt;p&gt;Either way, I was surprised to make it this far and I actually did learn quite a lot.&lt;/p&gt;

&lt;p&gt;That’s all for now. Thanks for reading!&lt;/p&gt;</description><author>ty-porter</author><pubDate>Mon, 16 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">/development/2024/12/16/advent-of-code-sql.html</guid></item><item><title>Customer Care: Supermodel AI Agents</title><link>https://www.danstroot.com/posts/2024-12-16-customer-care-supermodel-ai-agents</link><description>&lt;img alt="post image" src="https://danstroot.imgix.net/assets/blog/img/ai_models.png" /&gt;&lt;br /&gt;&lt;br /&gt;Customer care is undergoing a transformation unlike anything we've seen before. Today, artificial intelligence (AI) agents are proliferating across communication channels — handling real-time voice calls, managing text-based interactions such as chat, SMS, and email, and delivering a level of efficiency that traditional customer service teams often struggle to achieve. However, the horizon promises even more revolutionary possibilities.&lt;br /&gt;&lt;br /&gt;This post &lt;a href="https://www.danstroot.com/posts/2024-12-16-customer-care-supermodel-ai-agents"&gt;Customer Care: Supermodel AI Agents&lt;/a&gt; first appeared on &lt;a href="https://www.danstroot.com"&gt;Dan Stroot's Blog&lt;/a&gt;</description><author>Dan Stroot</author><pubDate>Mon, 16 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danstroot.com/posts/2024-12-16-customer-care-supermodel-ai-agents</guid></item><item><title>A color for every day</title><link>https://notes.npilk.com/daily-colors</link><description>To make daily updates feel different from one day to the next, I generated a unique color combination for every day of the year.</description><author>npilk // Notes</author><pubDate>Sun, 15 Dec 2024 23:15:00 GMT</pubDate><guid isPermaLink="true">https://notes.npilk.com/daily-colors</guid></item><item><title>badkeming: a site for kerning so bad it's keming</title><link>http://blog.jgc.org/2024/12/badkeming-site-for-kerning-so-bad-its.html</link><description>&lt;p&gt;I made another silly Tumblr (to go along with &lt;a href="https://moviecode.tumblr.com/"&gt;Movie Code&lt;/a&gt; and &lt;a href="https://lowbackgroundsteel.ai/"&gt;Low Background Steel&lt;/a&gt;). This times it's a Tumblr for all those font disasters and other typographical amusements. Welcome to the wonderful world of &lt;a href="https://badkeming.com"&gt;badkeming.com&lt;/a&gt;:&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://badkeming.com" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="420" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhOnAi31wLwP1beQLc-ylj8D2VpWVsJQkuyVcUMViFFpppM7BmbNgrB36PQWpxETwYpIP2PjkoJ-DnsDabdn5UOJoNvy-vh6oPlh2cISzOiqj-vmc-zfHKzSasC8ZamjWl7vwXU5ee2V7Jfj5wuQEBgfyOniA_1_3Z_RuTDFPv3zmTQVugR1HjOZA/w640-h420/badkeming-1.png" width="640" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="separator" style="clear: both; text-align: left;"&gt;Submissions gladly accepted.&lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Sun, 15 Dec 2024 18:16:23 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2024/12/badkeming-site-for-kerning-so-bad-its.html</guid></item><item><title>Nouveau site de cours</title><link>https://www.mylloon.fr/blog/p/2024/12/cours</link><description>&lt;p&gt;Ça y est, &lt;a href="mylloon.fr/cours"&gt;j’ai terminé la réécriture de mon site pour partager mes notes de cours&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;Jusqu’ici, j’utilisais l’application &lt;a href="https://github.com/xy2z/PineDocs/"&gt;PineDocs&lt;/a&gt;&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-pd" id="fnref-pd"&gt;1&lt;/a&gt;&lt;/sup&gt;.
J’ai fait de nombreuses contributions au projet et je tiens à encore remercier
&lt;a href="https://github.com/xy2z"&gt;xy2z&lt;/a&gt;&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-xy2z" id="fnref-xy2z"&gt;2&lt;/a&gt;&lt;/sup&gt; d’en avoir merge plus d’une.&lt;/p&gt;&lt;p&gt;Mais depuis un moment, je voulais changer. Déjà, PineDocs est écrit en PHP, un langage
que je ne connais pas. Je voulais aussi que le site soit mieux intégré à mon
nouveau site (l’actuel) au lieu qu’il soit séparé du reste. Le style ne me
convenait pas plus que ça ; et c’était un beau défi pour moi.&lt;/p&gt;&lt;p&gt;Alors voilà, avec les briques que j’avais écrites pour la partie “blog”,
quelques adaptations et beaucoup de corrections de bugs, j’ai enfin terminé. Ce
n’est pas parfait, mais c’est suffisant.&lt;/p&gt;&lt;p&gt;Les problèmes sont là : la recherche est &lt;em&gt;relativement&lt;/em&gt; lente (car faite en local),
et la construction de l’arbre des fichiers est faite en local,
alors plus le dossier partagé est gros, plus l’arbre l’est aussi.&lt;/p&gt;&lt;p&gt;Mais en même temps, enfin fini les problèmes entre le Markdown et les
formules &lt;span&gt;\LaTeX&lt;/span&gt;. Le back-end écrit en Rust est quand même plus rapide, surtout
quand la page demandée est mise en cache (qui est côté serveur, contrairement à
PineDocs qui le faisait côté client). Le style entre mon site “principal” et
la partie “cours” est désormais harmonisé et je suis plutôt content du résultat.
Enfin, le site de cours est disponible dans un thème clair !&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-light" id="fnref-light"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;&lt;p&gt;Aussi, je peux dorénavant imbriquer des fichiers Markdown entre eux. J’ai l’habitude
d’avoir chaque créneau de cours séparé les uns des autres et d’avoir un “méga-fichier”
qui cite tous mes autres fichiers de cours. Il est donc à présent proprement rendu
et facilement imprimable grâce à une jolie feuille CSS qui adapte mes pages à l’impression.&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-print" id="fnref-print"&gt;4&lt;/a&gt;&lt;/sup&gt;
Ce que j’avais fait pour le reste de mon site aussi est utilisé pour la réécriture&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-rewrite" id="fnref-rewrite"&gt;5&lt;/a&gt;&lt;/sup&gt;,
notamment mon “obfuscateur d’e-mail”&lt;sup class="footnote-ref"&gt;&lt;a href="#fn-mail" id="fnref-mail"&gt;6&lt;/a&gt;&lt;/sup&gt; qui est basique, mais c’est toujours mieux que
rien – et si ça peut passer quelques bots, c’est ça de gagner.&lt;/p&gt;&lt;p&gt;J’ai commencé à penser à la réécriture le 16 avril 2023, la PR qui a initié le
boulot a été ouverte le 31 octobre 2023 et a été merge le 1&lt;sup&gt;er&lt;/sup&gt; avril 2024, et c’est
aujourd’hui que l’état de la réécriture est dans un assez bon état pour être utilisable.&lt;/p&gt;&lt;p&gt;En ce qui concerne mon ancien site de cours, je vais le laisser en place pendant
un petit moment (peut-être jusqu’à la fin des fêtes de fin d’année) et je
pointerais l’URL vers la nouvelle version le temps venu.&lt;/p&gt;&lt;section class="footnotes"&gt;&lt;ol&gt;&lt;li id="fn-pd"&gt;&lt;p&gt;“A fast and lightweight site for viewing files.” &lt;a class="footnote-backref" href="#fnref-pd"&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id="fn-xy2z"&gt;&lt;p&gt;Je pense qu’il avait move-on du projet, mais il a quand même
pris le temps de vérifier ce que je faisais, un grand merci à toi ❤️. &lt;a class="footnote-backref" href="#fnref-xy2z"&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id="fn-light"&gt;&lt;p&gt;C’était particulièrement ironique parce que mon PC entier est souvent dans
un thème clair. &lt;a class="footnote-backref" href="#fnref-light"&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id="fn-print"&gt;&lt;p&gt;C’est important de pouvoir imprimer le cours, surtout pour les examens. &lt;a class="footnote-backref" href="#fnref-print"&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id="fn-rewrite"&gt;&lt;p&gt;On peut citer aussi le fait que mon site de cours est donc
désormais disponible sur Tor… &lt;a class="footnote-backref" href="#fnref-rewrite"&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id="fn-mail"&gt;&lt;p&gt;Rien de dingue, il remplace le &lt;code&gt;@&lt;/code&gt; par &lt;code&gt;(at)&lt;/code&gt;. Je veux éviter de faire des
trucs trop fancy pour que les gens sans JS soient quand même dans la capacité de voir mon site.
&lt;a href="https://git.mylloon.fr/Anri/mylloon.fr/src/branch/main/static/js/mail_obfuscation.js"&gt;https://git.mylloon.fr/Anri/mylloon.fr/src/branch/main/static/js/mail_obfuscation.js&lt;/a&gt; &lt;a class="footnote-backref" href="#fnref-mail"&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/section&gt;</description><author>Blog d'Anri</author><pubDate>Sun, 15 Dec 2024 14:12:00 GMT</pubDate><guid isPermaLink="true">https://www.mylloon.fr/blog/p/2024/12/cours</guid></item><item><title>The magic of Go: a tale of stones and connection</title><link>https://dostoynikov.bearblog.dev/the-magic-of-go-a-tale-of-stones-and-connection/</link><description>&lt;p&gt;Yesterday, I was walking around a quiet countryside neighborhood when I saw a sign for a "Go Club." For those who don’t know, Go is a strategic board game also called Baduk in Korean and Weiqi in Chinese.&lt;/p&gt;
&lt;p&gt;I have always been fascinated by Go because of its elegant mechanics and philosophy. Excited, I immediately went inside to say hello.&lt;/p&gt;
&lt;p&gt;Inside, four people were playing—two middle school students and two elderly men, both over 70. One of the elders, Naoki-san, who turned out to be the owner, left his game unfinished to greet me.&lt;/p&gt;
&lt;p&gt;We introduced ourselves and had a brief chat. Naoki-san then invited me to play a round. Go has a handicap system where weaker players start with more stones to balance the game. Since his level is 8-dan—extremely advanced—I started with 9 stones. For reference, my level is around 9-kyu, which is quite amateur! It was clear I was going to lose, but the game became a way for us to get to know each other through our styles of play.&lt;/p&gt;
&lt;p&gt;Our energy felt perfectly aligned, and it was such a fun experience. He also was looking really happy to have a foreigner-newcomer and I felt his happy energy and kindness all the time. Unfortunately, I had to leave the game unfinished to catch a train. Before I left, Naoki-san gifted me a beautiful Go set. I was shocked and overjoyed, especially since I had been looking for one recently.&lt;/p&gt;
&lt;p&gt;His kindness left a deep impression on me. He even walked me all the way to the door with a big, cheerful smile saying that he will wait for me next week. His energy truly made my day.&lt;/p&gt;
&lt;p&gt;Now, I know where I’ll be spending my weekends. This experience reminded me of the beauty of connecting through shared interests and kindness. I plan to visit the club regularly and hope to reach 1-dan within a year as I continue playing Go.&lt;/p&gt;</description><author>ᓚᘏᗢdostoynikov</author><pubDate>Sun, 15 Dec 2024 04:56:00 GMT</pubDate><guid isPermaLink="true">https://dostoynikov.bearblog.dev/the-magic-of-go-a-tale-of-stones-and-connection/</guid></item><item><title>Why Multiple Competing Platforms Are Better Than One</title><link>https://robkohr.com/articles/why-multiple-competing-platforms-are-better-than-one</link><description>Why Multiple Competing Platforms Are Better Than One</description><author>RobKohr's Blog</author><pubDate>Sun, 15 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/why-multiple-competing-platforms-are-better-than-one</guid></item><item><title>AWS re:Invent2024 Aurora 发布了啥 – DSQL 篇</title><link>http://baotiao.github.io/2024/12/15/aurora-2024-1.html</link><description>这个是前年AWS re:Invent 2022 的内容, 有兴趣可以看这个链接: Aurora re:Invent 2022</description><author>做有积累的事情</author><pubDate>Sun, 15 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://baotiao.github.io/2024/12/15/aurora-2024-1.html</guid></item><item><title>Weighty phrases</title><link>https://evanfields.net/Weighty-Phrases/</link><description>I’m deeply fond of weighty phrases – little snippets with cultural gravitas and mystical overtones. Most of my favorites have biblical origin or are fragments of culturally foundational art, remixed memetically over the centuries until they become units of their own. A few of my favorites: Not by bread alone A thousand and one nights All men are created equal That the strong might not injure the weak Look upon my works, ye mighty, and despair One small step for man, one giant leap for mankind My brother’s keeper The truth will set you free A plague on both your houses Justice, justice you shall pursue</description><author>Evan Fields</author><pubDate>Sun, 15 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://evanfields.net/Weighty-Phrases/</guid></item><item><title>Automating ticket progression</title><link>https://paperless.blog/automating-ticket-progression</link><description>I forget to move tickets around the board when I’m supposed to. After working with 10+ ticketing systems for some 20 years, it’s still too alien to have to perform a manual step in a completely separate system at the same time as trying to concentrate on the development. Why not automate this process a bit?</description><author>Paperless</author><pubDate>Sun, 15 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://paperless.blog/automating-ticket-progression</guid></item><item><title>Individual Career Plan</title><link>https://bytepawn.com/individual-career-plan.html</link><description>&lt;p&gt;This post provides a practical Individual Career Plan template you can use to guide self-assessment, prioritize your professional development, and set meaningful goals that align with your vision for the future. By outlining clear objectives and action steps, you create a framework for continuous improvement and more informed career decisions. I use this same template for the data organization I lead, filled it out myself, and shared it with others to inspire them to do the same.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Desert road sunset" src="/images/desert-road-sunset.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 15 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/individual-career-plan.html</guid></item><item><title>Novellfika på Konditori Fågelsången - Fox 8 av George Saunders</title><link>https://liza.io/novellfika-p%C3%A5-konditori-f%C3%A5gels%C3%A5ngen-fox-8-av-george-saunders/</link><description>&lt;p&gt;Idag höll jag min tredje novellfika i Uppsala, och det var en särskild fika eftersom några vänner från Stockholm var med. Så vi hade både nya och gamla kompisar i diskussionen. Efter fikan gick några av oss hem till mig för lunch och lite mer prat.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Sat, 14 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/novellfika-p%C3%A5-konditori-f%C3%A5gels%C3%A5ngen-fox-8-av-george-saunders/</guid></item><item><title>Using LLMs to Explain Historical Code: FLOW-MATIC Investigation</title><link>https://ztoz.blog/posts/flow-matic-llm/</link><description>&lt;p&gt;The new generation of code assistance tools powered by Large Language Models (LLM)s may be useful in efficiently categorizing and translating historical software corpora. In this qualitative study, we examine LLM performance in explaining and translating FLOW-MATIC programs. We also test their reliability by injecting OCR-like errors into the text.  Released in 1958, FLOW-MATIC targeted business applications and used a heavily English-inspired syntax that later influenced COBOL. We find that the models perform inconsistently for describing the key aspects of a program but fare better when translating business logic into SQL. LLMs appear robust against OCR-like errors in the source code, but we caution against relying on the current technology.&lt;/p&gt;
&lt;figure&gt;&lt;img src="Univac.Flowmatic.1957.102646140.fc.lg.jpg" /&gt;&lt;figcaption&gt;
            &lt;h4&gt;FLOW-MATIC Brochure&lt;/h4&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;
&lt;p&gt;Historical source code may contain insights into the development of algorithms,  programming practices, and shed light into the computational needs and pursuits of organizations. However, this same source code is often poorly cataloged, unavailable in digital form (e.g. exists in the form of paper tape or punch cards), and will usually be written in archaic programming languages, thus frustrating efforts to understand and interpret it. In order for researchers to effectively &amp;ldquo;triage&amp;rdquo; piles of artifacts and obtain a limited understanding of the contents, researchers might use an LLM to help explain and translate the code.&lt;/p&gt;
&lt;h3 id="why-flow-matic"&gt;Why FLOW-MATIC?&lt;/h3&gt;
&lt;p&gt;Developed by Dr. Grace Hopper and her team at Reminton Rand from around 1955 to 1958, FLOW-MATIC was an early language targeted at business applications. Internally, the language was called B-0 and experimented with an English-like syntax. Using approximately 33 verbs and coupled with an external data description language, programmers could implement sequential data processing logic (Sammet 1969).&lt;/p&gt;
&lt;p&gt;We chose FLOW-MATIC for this study because:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The language is sufficiently obscure that there is unlikely to be any special-case logic for the language, and&lt;/li&gt;
&lt;li&gt;The manual (Rand 1958) contains three highly documented programs that perform typical business logic, and&lt;/li&gt;
&lt;li&gt;The English syntax may be generalizable to text mining of programming or process specifications.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Although FLOW-MATIC is an obscure and dead programming language (even Rosetta Code doesn&amp;rsquo;t include any examples), these programs and associated commentary may have been included in the AI&amp;rsquo;s background corpus. The manual is available online as a PDF and (Sammet 1969) reprints Problem 1. We have also seen snippets of the code incorporated into various online articles on the language and Grace Hopper&amp;rsquo;s history. However, we think it is safe to assume no engineering effort has been made to improve FLOW-MATIC related answers.&lt;/p&gt;
&lt;h2 id="methodology"&gt;Methodology&lt;/h2&gt;
&lt;p&gt;We extracted three FLOW-MATIC programs from a PDF of (Rand 1958) and manually corrected translation errors to the best of our ability. Then, using the AI Assistant tool as part of IntelliJ, we sent select prompts to different AI models, attaching the FLOW-MATIC programs as part of the request.&lt;/p&gt;
&lt;p&gt;For the corruption studies, we ran the original source code through our &lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/corrupt.py?ref_type=heads"&gt;corrupt.py&lt;/a&gt; script. This tool randomly selects a corruption from a list and applies it to the text. If the corruption does not apply (i.e. produced no edits), then a different corruption was selected until the number of edits matches the desired number.&lt;/p&gt;
&lt;p&gt;Source code for the programs, the corrupted sources, the AI model output, and our corruption script are available within the &lt;a href="https://gitlab.com/jeffrey_starr/flow-matic"&gt;Gitlab repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Testing LLMs is &lt;a href="https://cacm.acm.org/news/is-it-possible-to-truly-understand-performance-in-llms/"&gt;tricky&lt;/a&gt;. LLMs are stochastic and highly sensitive to variations in prompts. As an apologetically qualitative study, our task specification is vague and we lack clear, rigorous performance metrics.&lt;/p&gt;
&lt;h2 id="explaining-and-translating-historical-code"&gt;Explaining and Translating Historical Code&lt;/h2&gt;
&lt;p&gt;For this task, we asked the LLM to produce an English summary of a program. We are less interested in a breakdown of individual lines, but rather succinct statements to the key aspects of the business logic, the data model, and pre and post conditions of the input and output.&lt;/p&gt;
&lt;h3 id="problem-1---join-inventory-and-prices"&gt;Problem 1 - Join Inventory and Prices&lt;/h3&gt;
&lt;p&gt;The first program described in (Rand 1958) joins an ordered sequence of inventory records with an ordered sequence of price records, yielding a file containing priced inventory and a file containing unpriced inventory. The program requires 18 steps and would have been accompanied by a series of packets describing the input and output data formats.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-flowmatic"&gt;(0) INPUT INVENTORY FILE-A PRICE FILE-B ; OUTPUT PRICED-INV FILE-C UNPRICED-INV FILE-D ; HSP D .

(1) COMPARE PRODUCT-NO (A) WITH PRODUCT-NO (B) ; IF GREATER GO TO OPERATION 10 ; IF EQUAL GO TO OPERATION 5 ; OTHERWISE GO TO OPERATION 2 .

(2) TRANSFER A TO D .

(3) WRITE-ITEM D .

(4) JUMP TO OPERATION 8 .

(5) TRANSFER A TO C .

(6) MOVE UNIT-PRICE (B) TO UNIT-PRICE (C) .

(7) WRITE-ITEM C .

(8) READ-ITEM A ; IF END OF DATA GO TO OPERATION 14 .

(9) JUMP TO OPERATION 1 .

(10) READ-ITEM B ; IF END OF DATA GO TO OPERATION 12 .

(11) JUMP TO OPERATION 1 .

(12) SET OPERATION 9 TO GO TO OPERATION 2 .

(13) JUMP TO OPERATION 2 .

(14) TEST PRODUCT-NO (B) AGAINST ZZZZZZZZZZZZ ; IF EQUAL GO TO OPERATION 16 ; OTHERWISE GO TO OPERATION 15 .

(15) REWIND B .

(16) CLOSE-OUT FILES C , D .

(17) STOP . (END)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notable aspects of this program:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There are two input files, inventory and prices. The output consists of a file with joined inventory data and pricing data and a secondary output of inventory lacking a price.&lt;/li&gt;
&lt;li&gt;Product Numbers (PRODUCT-NO) and Unit Price (UNIT-PRICE) are the only two fields referenced within the code.&lt;/li&gt;
&lt;li&gt;Input files are assumed to be sorted. If this assumption is violated, the unpriced output will be full of false positives.&lt;/li&gt;
&lt;li&gt;Each product in this version will have exactly one inventory record. This constraint is relaxed in later programs.&lt;/li&gt;
&lt;li&gt;The program uses self-modifying code as step 9 is overwritten in step 12.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;HSP D&lt;/code&gt; refers to the high-speed printer present on the UNIVAC. The instruction &amp;ldquo;redirects&amp;rdquo; output to the printer rather than a tape.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="llm-summaries"&gt;LLM Summaries&lt;/h4&gt;
&lt;p&gt;Using the prompt &lt;code&gt;Explain the code at the current caret&lt;/code&gt;, all three LLM models produced a detailed explanation covering each line of code. All three models referenced &amp;lsquo;ZZZZZZZZZZZZ&amp;rsquo; as a special marker. The final summaries from each model (below) all invoke matching entries between an inventory and pricing file or data and that prices may be missing.&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#openai-gpt-4o"&gt;openai-gpt-4o&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&amp;ldquo;This sequence executes a comparison loop to match items between an inventory and a pricing file, writing matched items with prices to one output file and unmatched items to another. It handles end-of-file conditions elegantly, ensuring all items are processed properly.&amp;rdquo;&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#google-chat-gemini-pro-15"&gt;google-chat-gemini-pro-1.5&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&amp;ldquo;In essence, this code implements a matching algorithm, similar to a merge join in database systems. It efficiently processes inventory and pricing data, handling scenarios where prices might be missing for certain items.&amp;rdquo;&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#openai-chat-gpt"&gt;openai-chat-gpt&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&amp;ldquo;Overall, the code snippet describes a simple matching process between inventory items and their prices, handling cases where prices may be missing in the price file.&amp;rdquo;&lt;/dd&gt;
&lt;/dl&gt;
&lt;h4 id="translation-into-sql"&gt;Translation into SQL&lt;/h4&gt;
&lt;p&gt;We used the prompt &lt;code&gt;Translate the code at the current caret into SQL&lt;/code&gt; to translate Problem 1 into SQL. A good translation should capture the business intent, that of a join, as well as features of the data model.&lt;/p&gt;
&lt;p&gt;We observed:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#openai-gpt-4o-3"&gt;openai-gpt-4o&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;The output features two inserts based on joins (implicit inner join and left join) into a priced and unpriced tables. The output includes a &amp;lsquo;quantity&amp;rsquo; field which is not present in the original code, although an inventory data record is highly likely to include such a field.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#google-chat-gemini-pro-15-2"&gt;google-chat-gemini-pro-1.5&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;The output uses procedural SQL features and attempts to match the original code feature-by-feature, which requires more lines than the original FLOW-MATIC version. The only fields referenced are the product number and the unit price. Includes an &amp;lsquo;operation_9_target&amp;rsquo; for emulating the self-modifying code, but the variable is only written, never read. The code also includes a useless block for checking for the &amp;lsquo;ZZZZZZZZZZZZ&amp;rsquo; end of data marker. This version also declares product numbers as &lt;code&gt;VARCHAR(255)&lt;/code&gt; and prices as &lt;code&gt;DECIMAL(10,2)&lt;/code&gt; for which there is no a priori support.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#google-chat-gemini-flash-15"&gt;google-chat-gemini-flash-1.5&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Similar to the openai-gpt-4o output, except the inner join is explicit and the code does not include a &amp;lsquo;quantity&amp;rsquo; field. Of the three outputs, this is the cleanest version.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;Translation may require the creation of entities not present in the original, such as table names, and changes to formatting, such as dashes to underscores, to be more idiomatic. However, ideally, insertion of content that relies on background knowledge rather than the source itself should be annotated.&lt;/p&gt;
&lt;h3 id="problem-2---treat-duplicate-inventory-as-errors"&gt;Problem 2 - Treat Duplicate Inventory as Errors&lt;/h3&gt;
&lt;p&gt;Problem 2 extends Problem 1 by allowing duplicate inventory records. If a duplicate record is found, the record is sent to an error file. We performed some &lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#problem-2"&gt;limited testing using Problem 2&lt;/a&gt;, but found it provided limited additional insight versus Problem 1 and Problem 3, so are ignoring it in the interests of space.&lt;/p&gt;
&lt;h3 id="problem-3---join-inventory-at-multiple-sites-with-prices"&gt;Problem 3 - Join Inventory at Multiple Sites with Prices&lt;/h3&gt;
&lt;p&gt;Problem 3 extends Problem 1 by allowing duplicate inventory records. Unlike in Problem 2, duplicate inventory records are meaningful and quantities are summed across records. (Conceptually, this represents inventory at multiple sites and we are calculating the global quantity.) Problem 3 also adds an &amp;lsquo;extended price,&amp;rsquo; to the output. Within the manual, the use of &lt;code&gt;X-1&lt;/code&gt; is to demonstrate how a program may call out to non-FLOW-MATIC code even though the logic (multiplying the quantity by the price) could have been accomplished within the standard code.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-flowmatic"&gt;(0) INPUT INVENTORY FILE-A PRICE FILE-B ; OUTPUT PRICED-INV FILE-C UNPRICED-INV FILE-D ; HSP D .

(1) COMPARE PRODUCT-NO (A) WITH PRODUCT-NO (B) ; IF GREATER GO TO OPERATION 21 ; IF EQUAL GO TO OPERATION 5 ; OTHERWISE GO TO OPERATION 2 .

(2) TRANSFER A TO D .

(3) SET OPERATION 13 TO GO TO OPERATION 18 .

(4) JUMP TO OPERATION 8 .

(5) TRANSFER A TO C .

(6) MOVE UNIT-PRICE (B) TO UNIT-PRICE (C) .

(7) SET OPERATION 13 TO GO TO OPERATION 14 .

(8) MOVE PRODUCT-NO (A) TO PRODUCT-NO (W) ; QUANTITY (A) TO QUANTITY (W) .

(9) READ-ITEM A ; IF END OF DATA GO TO OPERATION 23 .

(10) COMPARE PRODUCT-NO (A) WITH PRODUCT-NO (W) ; IF EQUAL GO TO OPERATION 11 ; OTHERWISE GO TO OPERATION 13 .

(11) X-1 ADD QUANTITY (A) TO STORED QUANTITY (W) .

(12) JUMP TO OPERATION 9 .

(13) JUMP TO OPERATION 14 .

(14) MOVE QUANTITY (W) TO QUANTITY (C) .

(15) X-1 COMPUTE EXTENDED PRICE AND INSERT IN C ITEM

(16) WRITE-ITEM C ,

(17) JUMP TO OPERATION 1 .

(18) MOVE QUANTITY (W) TO QUANTITY (D)

(19) WRITE-ITEM D .

(20) JUMP TO OPERATION 17 .

(21) READ-ITEM B ; IF END OF DATA GO TO OPERATION 1 .

(22) JUMP TO OPERATION 1 .

(23) EXECUTE OPERATION 13 THROUGH OPERATION 17 .

(24) TEST PRODUCT-NO (8) AGAINST ZZZZZZZZZZZZ ; IF EQUAL GO TO OPERATION 26 ; OTHERWISE GO TO OPERATION 25 .

(25) REWIND B .

(26) CLOSE-OUT FILES C , D .

(27) STOP . (END)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For reference to the reader, the first X-1 code (label 11) is defined as:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-univac-x1"&gt;B0W001
A-A001
C0W001
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These &lt;a href="https://ed-thelen.org/comp-hist/Univac1/Univac1.html#b13"&gt;instructions&lt;/a&gt; perform, using relative addresses:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Load the temporary value (W, offset 1) into register&lt;/li&gt;
&lt;li&gt;Add the value in file A (offset 1) to the register&lt;/li&gt;
&lt;li&gt;Copy the accumulated value to the temporary value (W, offset 1)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The second X-1 code (label 15) is defined as:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-univac-x1"&gt;L0C001
P0C002
J0C003
&lt;/code&gt;&lt;/pre&gt;&lt;ol&gt;
&lt;li&gt;Load into the L register from file C at offset 1&lt;/li&gt;
&lt;li&gt;Multiply (product) the L register and the contents from file C at offset 2, storing the result in A and X registers&lt;/li&gt;
&lt;li&gt;Store the result into file C (offset 3)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The layout of the data and the definitions of the extended functions are located in ancillary files, rather than the source code itself. Thus, unless the LLM cheated by reading the manual, &amp;ldquo;intuiting&amp;rdquo; the computation of an extended price is based on background knowledge of the likely implementation.&lt;/p&gt;
&lt;h4 id="llm-summaries-1"&gt;LLM Summaries&lt;/h4&gt;
&lt;p&gt;Similar to Problem 1, each model produced a detailed explanation covering each line. However, unlike Problem 1, their summaries of Problem 3 lack detail. Two of the explanations (openai-gpt-4o and google-chat-gemini-pro-1.5) simply describe the program as doing something with inventory and pricing, lacking any mention of aggregating data between inventory records or how joins are implemented. The third mentions the aggregation of inventory but drops references to pricing data.&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#openai-gpt-4o-4"&gt;openai-gpt-4o&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&amp;ldquo;The code provided appears to be using a structured way to process inventory and price files.&amp;rdquo;&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#google-chat-gemini-pro-15-3"&gt;google-chat-gemini-pro-1.5&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&amp;ldquo;This code snippet represents a pseudocode or a simplified representation of a program logic, likely for processing inventory and pricing data.&amp;rdquo;&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#google-chat-gemini-flash-15-1"&gt;google-chat-gemini-flash-1.5&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&amp;ldquo;Operations 8-12 form a loop that reads inventory items, and aggregates quantities for each unique product number by storing the running total quantity in temporary storage (&lt;code&gt;W&lt;/code&gt;). When a different product number is encountered, the loop breaks, and the aggregated quantity will be processed and written to the appropriate output file. This technique efficiently combines quantities for the same product before writing to the output file, avoiding redundant entries.&amp;rdquo;&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;None of the models provide the same quality of summary as they did for Problem 1.&lt;/p&gt;
&lt;h4 id="translation-into-sql-1"&gt;Translation into SQL&lt;/h4&gt;
&lt;p&gt;Similar to the task in Problem 1, each model was asked to translate the code into SQL. Our observations:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#openai-gpt-4o-4"&gt;openai-gpt-4o&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Accumulates inventory across multiple inventory records. Treats extended price as the product of inventory and unit price. Uses inner joins and left joins to differentiate the presence of pricing data. Uses common table expressions.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#google-chat-gemini-pro-15-3"&gt;google-chat-gemini-pro-1.5&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Very similar to openai-gpt-4o.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#google-chat-gemini-flash-15-1"&gt;google-chat-gemini-flash-1.5&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Very similar to both except it does not use common table expressions.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;While the different models generate different names for tables and vary slightly in their use of SQL, they all use the same joins and model the data in the same way. We consider the output to be high-quality.&lt;/p&gt;
&lt;h2 id="robustness-to-source-code-corruption"&gt;Robustness to Source Code Corruption&lt;/h2&gt;
&lt;p&gt;An LLM is not a compiler. While a compiler is expected to enforce the rules of a language and enforce consistency within the source code, an LLM matches the source code to a probabilistic model. Since historical source code is unlikely to come in a pristine digital form but rather a decayed backup or print-out, resistance to random corruptions is a beneficial quality. Furthermore, if we desire to triage or quickly check the contents of an archive, we do not need the power of a compiler.&lt;/p&gt;
&lt;p&gt;To simulate corruption of the source code, we created copies of the originals with a number of edits applied. The nature of the edit was based on our experiences correcting the programs from the PDF. Thus, the errors simulate optical character recognition errors rather than bit flips. The &lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/corrupt.py?ref_type=heads"&gt;script&lt;/a&gt; chose edits randomly from:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Changing zero (0) characters to O characters&lt;/li&gt;
&lt;li&gt;Change 1 to I&lt;/li&gt;
&lt;li&gt;Change 8 to B&lt;/li&gt;
&lt;li&gt;Change ; to :&lt;/li&gt;
&lt;li&gt;Remove a space (e.g. &lt;code&gt;A B&lt;/code&gt; to &lt;code&gt;AB&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Add a space (e.g. &lt;code&gt;AB&lt;/code&gt; to &lt;code&gt;A B&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;As the number of edits increased, eventually all the characters were swapped and the code was simply adding and removing spaces in random locations. The number of edits varied from 81 (approximately 10% of the total characters), to 243, and 486 (approximately 50%).&lt;/p&gt;
&lt;p&gt;For this exercise, we focused on a single AI model, openai-gpt-4o, and a single original source program, Problem 1. We chose to focus on openai-gpt-4o for convenience.&lt;/p&gt;
&lt;h3 id="llm-summaries-2"&gt;LLM Summaries&lt;/h3&gt;
&lt;p&gt;When asked to explain three corruptions of Problem 1, each with 81 edits, the model&amp;rsquo;s &lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#explain-corrupted-code-robustness-test"&gt;summaries&lt;/a&gt; stated the input files were inventory and pricing data in each case. In only one case does the summary state the output has priced and unpriced categories. In the other two cases, the summary merely describes that sequential or batch processing is in play.&lt;/p&gt;
&lt;p&gt;In contrast, this model provided the priced and unpriced insight for the original source code, as did the other two AI models. Similar to performance from Problem 1 to Problem 3, corruptions or other slight increases in functionality led to a drop in the quality of output.&lt;/p&gt;
&lt;h3 id="translation-into-sql-2"&gt;Translation into SQL&lt;/h3&gt;
&lt;p&gt;Across six versions of Problem 1, three with 243 corruptions and three with 486 corruptions, the model produced the same &lt;a href="https://gitlab.com/jeffrey_starr/flow-matic/-/blob/master/ai-output.md?ref_type=heads#translate-to-sql-robustness-tests"&gt;SQL output consistently&lt;/a&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-- Step 1: Identify and insert unpriced inventory items
INSERT INTO unpriced_inventory (product_no, other_columns)
SELECT i.product_no, i.other_columns
FROM inventory i
LEFT JOIN price p ON i.product_no = p.product_no
WHERE p.product_no IS NULL;

-- Step 2: Identify and insert priced inventory items
INSERT INTO priced_inventory (product_no, unit_price, other_columns)
SELECT i.product_no, p.unit_price, i.other_columns
FROM inventory i
INNER JOIN price p ON i.product_no = p.product_no;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If we contrast this to the model&amp;rsquo;s output with the original file:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;-- SQL for Priced Inventory (FILE C)
INSERT INTO PRICED_INV_TABLE_C (product_no, quantity, unit_price)
SELECT a.product_no, a.quantity, b.unit_price
FROM INVENTORY_TABLE_A a
JOIN PRICE_TABLE_B b ON a.product_no = b.product_no;

-- SQL for Unpriced Inventory (FILE D)
INSERT INTO UNPRICED_INV_TABLE_D (product_no, quantity)
SELECT a.product_no, a.quantity
FROM INVENTORY_TABLE_A a
LEFT JOIN PRICE_TABLE_B b ON a.product_no = b.product_no
WHERE b.product_no IS NULL;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Apart from the changes in table names and order of outputs, the output from the corrupted sources includes a &lt;code&gt;other_columns&lt;/code&gt; column, while the original has a &lt;code&gt;quantity&lt;/code&gt; column. Neither &lt;code&gt;quantity&lt;/code&gt; nor &lt;code&gt;other_columns&lt;/code&gt; exists in the original FLOW-MATIC source code. As a clue to the developer that the desired record may include additional fields, &lt;code&gt;other_columns&lt;/code&gt; is more explicit than &lt;code&gt;quantity&lt;/code&gt;, although &lt;code&gt;quantity&lt;/code&gt; is a believable column for an inventory table.&lt;/p&gt;
&lt;p&gt;Using Problem 3 as the base, in six cases with the same variations of edits the model also produces consistent output. This output includes an &lt;code&gt;other_columns&lt;/code&gt; field but otherwise does not add additional columns. This version lacks Problem 3&amp;rsquo;s handling of multiple inventory records, thus marking a regression in functionality.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;The LLM models appear to be unreliable as a tool to quickly summarize programs or translate them into other forms while retaining key aspects of the original logic. While their output can be highly consistent in the presence of random errors, their ability to capture and communicate key aspects of the programs can quickly decay to uselessness.&lt;/p&gt;
&lt;p&gt;Thus, researchers should be wary about using LLMs as a tool for researching historical source code. Technologically, improvements in models and workflows might render LLMs a reliable tool.&lt;/p&gt;
&lt;p&gt;We believe future quantitative studies should include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Measures of quality for the output, including key aspects included, contextual aspects not referenced but included, and aspects added without utility&lt;/li&gt;
&lt;li&gt;Measures of the narrative signal to noise ratio&lt;/li&gt;
&lt;li&gt;Rendering to a &amp;ldquo;canonical&amp;rdquo; form to simplify bulk analysis&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.computerhistory.org/collections/catalog/102646140"&gt;FLOW-MATIC Brochure&lt;/a&gt; Courtesy of Computer History Museum.&lt;/p&gt;
&lt;p&gt;(Rand 1958) Remington Rand Univac. 1958. &lt;em&gt;UNIVAC FLOW-MATIC Programming System.&lt;/em&gt; &lt;a href="https://archive.org/details/bitsavers_univacflowProgrammingSystem1958_9367413"&gt;archive.org&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(Sammet 1969) Sammet, Jean E. 1969. &lt;em&gt;Programming Languages: History and Fundamentals.&lt;/em&gt; Englewood Cliffs, N.J.: Prentice-Hall. &lt;a href="https://archive.org/details/programminglangu00unse"&gt;archive.org&lt;/a&gt;&lt;/p&gt;</description><author>ℤ→ℤ</author><pubDate>Sat, 14 Dec 2024 22:18:37 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/flow-matic-llm/</guid></item><item><title>Three Nine</title><link>https://www.sunilshenoy.com/2024/12/15/three-nine.html</link><description>&lt;p&gt;Birthdays seem to come by much quicker than before. Three nine. Another year closer to the next milestone birthday. I wondered if I would still be as excited about birthdays, and I’m happy to report that the excitement continues.&lt;/p&gt;
&lt;p&gt;While looking for last year’s post on the blog, I didn’t have to scroll far—it’s clear I haven’t been writing much this year. I always question why I don’t write here more, knowing that I enjoy the entire process.&lt;/p&gt;
&lt;p&gt;Sitting down to write always brings a huge smile to my face. It gives me the chance to go through my calendar and reflect on everything I’ve been up to this year. When I read this post next year, I’ll get to relive these moments all over again.&lt;/p&gt;
&lt;p&gt;A few years ago, I decided to stop creating long to-do lists for the year and instead focus on themes. The idea was to do more of the things I enjoy. I’m happy to say that this year, I managed to stick to all the themes I set out for myself.&lt;/p&gt;
&lt;h3 id="create"&gt;&lt;strong&gt;Create&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;This year, I built and launched two new applications, and I’m happy with the work I’ve done. There are two more applications in progress, and I hope to share more about them soon. Overall, I’m pleased with how the “create” theme shaped up this year.&lt;/p&gt;
&lt;p&gt;I spent time relearning Ruby on Rails, Swift, and SwiftUI. On top of that, I improved my skills in React Native, Node.js, and debugging software.&lt;/p&gt;
&lt;h3 id="travel"&gt;&lt;strong&gt;Travel&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;A few days after my birthday last year, I went to New Zealand. As I write this, I’ve booked another trip to visit on January 3rd. I can’t get enough of that beautiful country. As much as I love Australia, New Zealand has a special place in my heart.&lt;/p&gt;
&lt;p&gt;Here’s where I traveled to this year:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;New Zealand&lt;/li&gt;
&lt;li&gt;Thailand&lt;/li&gt;
&lt;li&gt;Uluru&lt;/li&gt;
&lt;li&gt;Gold Coast&lt;/li&gt;
&lt;li&gt;Melbourne&lt;/li&gt;
&lt;li&gt;India&lt;/li&gt;
&lt;li&gt;Poland&lt;/li&gt;
&lt;li&gt;Berlin&lt;/li&gt;
&lt;li&gt;The Hague, Netherlands&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It was wonderful catching up with friends and family, some of whom I hadn’t seen in nine years. On the way back, I promised myself to visit more places where friends have moved.&lt;/p&gt;
&lt;h3 id="fitness"&gt;&lt;strong&gt;Fitness&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;December, January, and the first half of February were amazing for my fitness—I was the fittest I’ve ever been. I even posted a picture of myself from a trip on Instagram, and it’s now one of my favorites. Whenever someone asks if I’ve ever had abs, I proudly show them that picture.&lt;/p&gt;
&lt;p&gt;However, I also experienced my most unfit period this year. After returning from my trip, I stopped working out for a few months and gained a lot of weight. The see-saw of weight gain and loss is something I’ve come to terms with.&lt;/p&gt;
&lt;p&gt;On a positive note, I’ve been back on track for the past three months, and I’m now as fit as I was in the early part of the year. With my cousin’s wedding in December next year, I’ve told her I’ll try to be as ripped as possible for the event. I’ve got one year to stay consistent and aim for that goal.&lt;/p&gt;
&lt;p&gt;This year reminded me that fitness is a journey, not a destination, and I&amp;rsquo;m proud of my progress.&lt;/p&gt;
&lt;h3 id="love"&gt;&lt;strong&gt;Love&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Another unsuccessful year in this department. A heartbreak aside, there’s nothing significant to report.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s to seeing what 2025 has in store for this chapter of my life.&lt;/p&gt;
&lt;h3 id="overall"&gt;&lt;strong&gt;Overall&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;2024 has been a really good year. My finances are back on track, I’m enjoying the work I’m doing, I love where I’m living, and I feel quite optimistic about the future. I can’t wait to share more about the software I’ve been working on over the last three months.&lt;/p&gt;
&lt;h3 id="themes-for-2025"&gt;&lt;strong&gt;Themes for 2025&lt;/strong&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Travel:&lt;/strong&gt; I’ve booked a few tickets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fitness:&lt;/strong&gt; Prepare for my cousin’s wedding in December.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Love:&lt;/strong&gt; Let’s see how this goes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Launch/Enhance:&lt;/strong&gt; After spending 2024 creating, it’s time to launch, listen to customer feedback, and enhance.&lt;/li&gt;
&lt;/ul&gt;</description><author>Sunil Shenoy</author><pubDate>Sat, 14 Dec 2024 21:25:00 GMT</pubDate><guid isPermaLink="true">https://www.sunilshenoy.com/2024/12/15/three-nine.html</guid></item><item><title>Why South Korea’s Victory over the North will Prove to be Fleeting</title><link>https://marctreagan.medium.com/why-south-koreas-victory-over-the-north-will-prove-to-be-fleeting-4d2b5a818942?source=rss-39742e0e4fea------2</link><description>&lt;p&gt;How many times have you been scrolling on social media and seen the following image:&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/720/0*6kbVRXMtfJKrYtgx" /&gt;&lt;/figure&gt;&lt;p&gt;Free market capitalists love to cite the Korean example for why capitalism has triumphed over communism. Korea has proved to be a unique test case because the people are ethnically homogenous and the country was arbitrarily divided down the middle 71 years ago when the Korean war ended. It is very hard to run social experiments on a countrywide level but sometimes fate delivers the goods. East/West Germany and China vs. Taiwan are a few other notable examples.&lt;/p&gt;&lt;p&gt;On every objective metric imaginable South Korea appears to be victorious. GDP per capita is around 23x higher: $36,000 vs $1,600. The average lifespan in the south is 83 years vs. 72 years in the north. The infant mortality rate per 1000 births is 3 in the south whereas it is 15 in the north. The list goes on: infrastructure, automobile ownership, internet access, medical care, working conditions, human rights, diplomatic relations, and cultural impacts on the rest of the world. People all over the world are listening to K-pop and watching Squid Game on Netflix.&lt;/p&gt;&lt;p&gt;As a rabid free market capitalist myself I have long cited this example as to why capitalism is superior to communism. But my declaration of victory was premature. If North Korea can continue to simply trudge along in a few generations they can conquer the South without firing a shot.&lt;/p&gt;&lt;p&gt;The average fertility rate in the North is 1.8 births per woman while in the South it is .7 births. A fertility rate of just over two is needed to maintain current population levels. This is because each woman needs one child to replace herself and one to replace a man. Hopefully our hypothetical couple is in a committed relationship, but at this point I’m not going to be choosy. If current birth rates persist in 4 generations the population of South Korea will decline by 94%. This extinction level event is occurring right now in slow motion.&lt;/p&gt;&lt;p&gt;Perhaps superior technology and weapons systems will allow the south to maintain their independence for a while. Back when the sun never set on the British empire a Sudanese army was threatening the highly lucrative British colony in Egypt. For the good of the empire the British decided they needed to do something about these pesky natives.&lt;/p&gt;&lt;p&gt;In 1898 at the battle of Omdurman 25,000 combined British and Egyptian forces fought 52,000 native Sudanese. The British had machine guns, artillery pieces, and breech loading rifles. The Sudanese forces meanwhile had rudimentary muskets, spears, and swords.&lt;/p&gt;&lt;p&gt;The battle was a triumph of western technology and tactics over crude native methods. The Sudanese forces suffered 28,000 casualties: 10,000 dead, 13,000 wounded, and 5,000 prisoners. The British suffered a few hundred casualties and 40 deaths.&lt;/p&gt;&lt;p&gt;Hilaire Belloc later so succinctly quipped: “Whatever happens, we have got the Maxim gun, and they have not.” The Maxim gun was the name for the British machine gun.&lt;/p&gt;&lt;p&gt;If the British had been outnumbered 100 to 1 at this battle I believe the outcome would have been different. Technology and tactics can go far in ensuring victory even when you are vastly outnumbered. But as Napoleon so famously said: “Quantity has a quality all it’s own.” Technology and tactics can only get you so far.&lt;/p&gt;&lt;p&gt;South Korea will find itself in this situation in a few short generations. North Korea has nukes while the South does not. The North invests heavily in their military. I have no doubt most of this money is squandered but when you outnumber the enemy 100 to 1 I’m betting on the Kim dynasty to prevail.&lt;/p&gt;&lt;p&gt;If we do not find a solution to collapsing birth rates in developed, democratic, and capitalist nations we will ultimately lose the fight. I had such great hope for enlightenment ideas and ways of living. The last few hundred years have been a momentous time for the freedom and rights of the individual. But if we cannot sustain our numbers in peacetime then something about our system is rotten to the core.&lt;/p&gt;&lt;p&gt;My fellow free market economists: we fought the good fight, we had a good run, it has been an honor serving beside you. But perhaps the rugged individualism that I so vociferously espouse has always been doomed to fail. Humans are social animals after all. Perhaps Marx was right all along.&lt;/p&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=4d2b5a818942" width="1" /&gt;</description><author>Stories by Marc Reagan on Medium</author><pubDate>Sat, 14 Dec 2024 10:08:36 GMT</pubDate><guid isPermaLink="true">https://marctreagan.medium.com/why-south-koreas-victory-over-the-north-will-prove-to-be-fleeting-4d2b5a818942?source=rss-39742e0e4fea------2</guid></item><item><title>OnlineOrNot Diaries 23</title><link>https://maxrozen.com/onlineornot-diaries-23</link><description>Working with big systems all day can slow you down.</description><author>Max Rozen</author><pubDate>Sat, 14 Dec 2024 09:10:00 GMT</pubDate><guid isPermaLink="true">https://maxrozen.com/onlineornot-diaries-23</guid></item><item><title>What's New in Emacs: Last Decade Edition</title><link>https://lambdaland.org/posts/2024-12-14_emacs_catchup/</link><description>&lt;p&gt;Emacs has come a long way in the past decade. This is meant as a guide to anyone who&amp;rsquo;s been using stock or near-stock Emacs for some years and wants a quick update on the new shiny stuff that comes bundled with Emacs.&lt;/p&gt;
&lt;p&gt;This guide assumes you are running Emacs 29, which was released in 2023.&lt;/p&gt;
&lt;h2 id="completion"&gt;
  Completion
  &lt;a class="anchor" href="#completion"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Completion is pervasive in Emacs: hit &lt;code&gt;TAB&lt;/code&gt; whenever you&amp;rsquo;re selecting a file (&lt;code&gt;C-x C-f&lt;/code&gt;) or running a command by name (&lt;code&gt;M-x&lt;/code&gt;) and the like, and completion kicks in. Emacs&amp;rsquo; built-in completion framework and interface have gotten a huge upgrade in recent years.&lt;/p&gt;
&lt;p&gt;If you hit &lt;code&gt;TAB&lt;/code&gt; a bunch of times when writing e.g. a file name, you&amp;rsquo;ll open up the &lt;code&gt;*Completions*&lt;/code&gt; buffer. Emacs 29 has lots of ways to configure this buffer to be much more useful than the default:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-emacs-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt enable-recursive-minibuffers &lt;span style="color: #8fbcbb;"&gt;t&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt completion-auto-help &lt;span style="color: #a3be8c;"&gt;'always&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt completions-max-height &lt;span style="color: #b48ead;"&gt;20&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt completions-format &lt;span style="color: #a3be8c;"&gt;'one-column&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt completion-auto-select &lt;span style="color: #a3be8c;"&gt;'second-tab&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s what each one does:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;code&gt;enable-recursive-minibuffers&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;You can interrupt doing something in the minibuffer with another minibuffer-using operation.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;completion-auto-help&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;The value &lt;code&gt;'always&lt;/code&gt; means always show the &lt;code&gt;*Completions*&lt;/code&gt; buffer when trying to complete; other options include &lt;code&gt;nil&lt;/code&gt; and &lt;code&gt;'lazy&lt;/code&gt;.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;completions-max-height&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;Controls how many lines high the &lt;code&gt;*Completions*&lt;/code&gt; buffer should be.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;completions-format&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;Put all completions in one column. I like this formatting better.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;completion-auto-select&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;Controls when to jump to the &lt;code&gt;*Completions*&lt;/code&gt; buffer automatically. The &lt;code&gt;'second-tab&lt;/code&gt; option is nice: the first &lt;code&gt;TAB&lt;/code&gt; opens the &lt;code&gt;*Completions*&lt;/code&gt; buffer, and if you want to select something from the list, you just hit &lt;code&gt;TAB&lt;/code&gt; again.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;That&amp;rsquo;s for minibuffer completion. Emacs also supports completion for whatever the cursor is on; Emacs calls this &amp;ldquo;completion-at-point&amp;rdquo;. Here&amp;rsquo;s how to get nice tab-complete behavior in Emacs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-emacs-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt tab-always-indent &lt;span style="color: #a3be8c;"&gt;'complete&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt completion-styles &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;basic initials &lt;span style="color: #88c0d0;"&gt;substring&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Setting &lt;code&gt;tab-always-indent&lt;/code&gt; to &lt;code&gt;'complete&lt;/code&gt; means that when you hit &lt;code&gt;TAB&lt;/code&gt;, Emacs will &lt;em&gt;first&lt;/em&gt; try to indent the current line. If the line is already indented, then Emacs will call the completion-at-point facilities of Emacs. Assuming you have the minibuffer completions set up as explained above, you can use the &lt;em&gt;same&lt;/em&gt; interface to complete in the minibuffer as well as tab-completion that you see in other editors.&lt;/p&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;These are just the built-in completion mechanisms. Emacs&amp;rsquo; framework has gotten a great upgrade that has allowed packages like &lt;a href="https://github.com/minad/vertico"&gt;Vertico&lt;/a&gt; (minibuffer completion) and &lt;a href="https://github.com/minad/corfu"&gt;Corfu&lt;/a&gt; (completion-at-point in a popup window) to flourish. Vertico and Corfu are two of &lt;a href="https://lambdaland.org/posts/2024-05-30_top_emacs_packages/"&gt;my favorite packages&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;completion-styles&lt;/code&gt; is neat: it takes a list of different &amp;ldquo;styles&amp;rdquo; that can be used when filtering candidates. The &lt;code&gt;initials&lt;/code&gt; is particularly fun: type &lt;code&gt;M-x dtw TAB&lt;/code&gt; and you should see the function &lt;code&gt;delete-trailing-whitespace&lt;/code&gt; in the &lt;code&gt;*Completions*&lt;/code&gt; buffer.&lt;/p&gt;
&lt;h2 id="editing-code"&gt;
  Editing code
  &lt;a class="anchor" href="#editing-code"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="smart-completion-jump-to-definition-etc-dot"&gt;
  Smart completion, jump-to-definition, etc.
  &lt;a class="anchor" href="#smart-completion-jump-to-definition-etc-dot"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Emacs 29 added the &lt;a href="https://github.com/joaotavora/eglot"&gt;Eglot&lt;/a&gt; (&amp;ldquo;Emacs polyGLOT&amp;rdquo;) package to the core distribution: Eglot is a lightweight &lt;a href="https://en.wikipedia.org/wiki/Language_Server_Protocol"&gt;Language Server Protocol (LSP)&lt;/a&gt; client. This lets Emacs talk to language servers like &lt;a href="https://clangd.llvm.org/"&gt;clangd&lt;/a&gt; or &lt;a href="https://github.com/rust-lang/rust-analyzer"&gt;rust-analyzer&lt;/a&gt; (etc.) to get things like good code completion, jump-to-definition, documentation, etc.&lt;/p&gt;
&lt;p&gt;Eglot is conservative: it doesn&amp;rsquo;t get in your way, and all the completion smarts come up only when you ask for them. Of course, it&amp;rsquo;s possible to make it more eager and behave a little more like VS Code, but that&amp;rsquo;s your choice. I personally use jump-to-definition more than any other feature.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re working on—say—a Rust project, install &lt;a href="https://github.com/rust-lang/rust-analyzer"&gt;rust-analyzer&lt;/a&gt; on your system, then open a file in a Rust project and run &lt;code&gt;M-x eglot&lt;/code&gt;. Now all of the completion-at-point mechanisms should be smart about the language you&amp;rsquo;re working with. You should also be able to jump to e.g. a function definition by putting your cursor on a function and invoking &lt;code&gt;xref-find-definitions&lt;/code&gt; (bound to &lt;code&gt;M-.&lt;/code&gt; by default.)&lt;/p&gt;
&lt;h3 id="projects"&gt;
  Projects
  &lt;a class="anchor" href="#projects"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Emacs now has &lt;code&gt;project.el&lt;/code&gt; package that adds some nice stuff for navigating projects. For example, the &lt;code&gt;project-find-file&lt;/code&gt; command is like &lt;code&gt;find-file&lt;/code&gt; but is restricted to files in the current project. This pairs nicely with fancier completing-read interfaces to let you find and jump to files without having to navigate the entire file hierarchy.&lt;/p&gt;
&lt;h3 id="tree-sitter"&gt;
  Tree-sitter
  &lt;a class="anchor" href="#tree-sitter"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://tree-sitter.github.io/tree-sitter/"&gt;Tree-sitter&lt;/a&gt; is a parser generator tool that is fast and—most importantly—works on incomplete inputs (e.g. a file you&amp;rsquo;re in the middle of editing). Emacs 29 added support for tree-sitter enabled modes. To be honest, it&amp;rsquo;s a little clunky still, but support is improving. (The Emacs 30 pre-release is already better than what 29 does.) Tree-sitter is too big for me to cover here; see &lt;a href="https://www.masteringemacs.org/article/how-to-get-started-tree-sitter"&gt;Mickey Petersen&amp;rsquo;s excellent article&lt;/a&gt; on the subject.&lt;/p&gt;
&lt;h2 id="editing-prose"&gt;
  Editing prose
  &lt;a class="anchor" href="#editing-prose"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;There&amp;rsquo;s a built-in dictionary feature in Emacs 28. Use this to look up words from &lt;a href="https://dict.org"&gt;https://dict.org&lt;/a&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-emacs-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt dictionary-server &lt;span style="color: #a3be8c;"&gt;"dict.org"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Use &lt;code&gt;M-x dictionary-lookup-definition&lt;/code&gt; to look up the word at point. You can of course bind this to a key for more convenience. (Looks like there&amp;rsquo;s also &lt;code&gt;dictionary-tooltip-mode&lt;/code&gt; which shows the definition of a word when you hover over it with your mouse. Fancy, and a bit too much for me. But very cool!)&lt;/p&gt;
&lt;p&gt;Going &lt;em&gt;really&lt;/em&gt; far back is &lt;code&gt;flyspell-mode&lt;/code&gt;: spellchecking while-you-type. Nothing special about that; what &lt;em&gt;is&lt;/em&gt; special is how easy it is to correct words: hit &lt;code&gt;C-;&lt;/code&gt; to cycle through corrections of the closest misspelled word. For code, there&amp;rsquo;s &lt;code&gt;flyspell-prog-mode&lt;/code&gt;, which does spellchecking in just comments and strings. Even though this has existed for a while, I don&amp;rsquo;t see it turned on very often.&lt;/p&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;I now use &lt;a href="https://github.com/minad/jinx"&gt;Jinx&lt;/a&gt; for my spellchecking needs. It&amp;rsquo;s faster and more flexible, but it draws a lot of inspiration from &lt;code&gt;flyspell-mode&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This mode is also a bit older, but I love &lt;code&gt;visual-line-mode&lt;/code&gt;: turning this on will soft-wrap lines at word boundaries and make all your motion commands behave according to &lt;em&gt;visual&lt;/em&gt; rather than &lt;em&gt;logical&lt;/em&gt; lines: e.g. if I have a really long line that is wider than my window, it will be wrapped—just like in any word processor—and pressing the arrow keys will move me to the character that is visually above the current one, even though it might be on the same line.&lt;/p&gt;
&lt;h2 id="general-improvements"&gt;
  General improvements
  &lt;a class="anchor" href="#general-improvements"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="new-themes"&gt;
  New themes
  &lt;a class="anchor" href="#new-themes"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Emacs comes with two new themes: &lt;code&gt;modus-operandi&lt;/code&gt; (light) and &lt;code&gt;modus-vivendi&lt;/code&gt; (dark). These themes have excellent contrast and are ment to conform to the highest levels of visual accessibility. Try them with &lt;code&gt;M-x load-theme RET modus-vivendi&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="package-manager-and-configuration"&gt;
  Package manager and configuration
  &lt;a class="anchor" href="#package-manager-and-configuration"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Emacs has a built-in package manager called &lt;code&gt;package.el&lt;/code&gt;. This makes it easy to install 3rd-party packages. Run &lt;code&gt;M-x list-packages&lt;/code&gt; to activate.&lt;/p&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;I have a list of &lt;a href="https://lambdaland.org/posts/2024-05-30_top_emacs_packages/"&gt;my favorite Emacs packages&lt;/a&gt;, which include (but are not limited to) &lt;a href="https://magit.vc/"&gt;Magit&lt;/a&gt;, &lt;a href="https://github.com/minad/vertico"&gt;Vertico&lt;/a&gt;, and &lt;a href="https://github.com/abo-abo/avy"&gt;Avy&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;There are several places you can get packages from; when &lt;code&gt;package.el&lt;/code&gt; came out it only supported packages from &lt;a href="https://elpa.gnu.org/"&gt;GNU ELPA&lt;/a&gt;. Recently &lt;a href="https://elpa.nongnu.org/"&gt;Non-GNU ELPA&lt;/a&gt; got added to the stock list which opens up a &lt;em&gt;huge&lt;/em&gt; set of packages such as the venerable &lt;a href="https://magit.vc/"&gt;Magit&lt;/a&gt; package. And of course, there&amp;rsquo;s &lt;a href="https://melpa.org"&gt;MELPA&lt;/a&gt;, which you have to &lt;a href="https://melpa.org/#/getting-started"&gt;add to your config yourself&lt;/a&gt; if you want packages from there:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-emacs-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;add-to-list &lt;span style="color: #a3be8c;"&gt;'package-archives&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"melpa"&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;.&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"https://melpa.org/packages/"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;t&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Emacs 28 added the awesome &lt;code&gt;use-package&lt;/code&gt; macro, which makes configuring packages really nice. Previously you might have written something like this to configure a package:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-emacs-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;require&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;'citar&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;define-key&lt;/span&gt; global-map &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;kbd &lt;span style="color: #a3be8c;"&gt;"C-c C-i"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;'citar-insert-citation&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;define-key&lt;/span&gt; global-map &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;kbd &lt;span style="color: #a3be8c;"&gt;"C-c C-d"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;'citar-dwim&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;add-hook &lt;span style="color: #a3be8c;"&gt;'org-mode-hook&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;'citar-capf-setup&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;add-hook &lt;span style="color: #a3be8c;"&gt;'LaTeX-mode&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;'citar-capf-setup&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;setq&lt;/span&gt; citar-bibliography  &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"~/Research/refs.bib"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now you can do this with a macro:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-emacs-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;use-package&lt;/span&gt; citar
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #81a1c1;"&gt;:bind&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"C-c C-i"&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;.&lt;/span&gt; citar-insert-citation&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"C-c C-d"&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;.&lt;/span&gt; citar-dwim&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #81a1c1;"&gt;:hook&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;org-mode LaTeX-mode&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;.&lt;/span&gt; citar-capf-setup&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #81a1c1;"&gt;:custom&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;citar-bibliography &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"~/Research/refs.bib"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;What you &lt;em&gt;don&amp;rsquo;t&lt;/em&gt; see is all the extra work that &lt;code&gt;use-package&lt;/code&gt; does behind the scenes to speed up startup times by lazily loading your package. There are lots of options for setting up hooks, different kinds of configuration, keymaps, etc.&lt;/p&gt;
&lt;h3 id="performance-and-utilities"&gt;
  Performance and utilities
  &lt;a class="anchor" href="#performance-and-utilities"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Emacs 27 added a native JSON parsing library. This meant that things like Eglot (and other LSP clients) got much snappier. Emacs 28 can compile Emacs Lisp code to native byte code. This means that all Emacs packages just run faster.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a lot more, but those are some of the biggest ones. Nice to see Emacs getting a lot of love.&lt;/p&gt;
&lt;h2 id="ecosystem"&gt;
  Ecosystem
  &lt;a class="anchor" href="#ecosystem"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Many of the improvements to the core have also lead to a flourishing of excellent third-party packages. Here are some of my favorite:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;a href="https://magit.vc"&gt;Magit&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;The only Git porcelain worth using. I was once a Git command-line purist. Then I found Magit and became a convert: unlike other Git porcelains, Magit hides &lt;em&gt;none&lt;/em&gt; of what Git can do. Moreover, it makes complicated operations (e.g. staging or reverting individual lines of a file) easy and exposes you to more of what Git can do.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://github.com/minad/vertico"&gt;Vertico&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;&amp;ldquo;Vertical completion&amp;rdquo;: wraps the standard completing-read interface with a nice interactive version. Thanks to the improvements to the completing-read API, all Emacs features that use completing-read just work.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://github.com/minad/consult"&gt;Consult&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Enhancements for a bunch of Emacs&amp;rsquo; UI: for example, &lt;code&gt;consult-buffer&lt;/code&gt; replaces the standard &lt;code&gt;switch-to-buffer&lt;/code&gt; with a version that shows you a live preview. Pairs nicely with Vertico.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://github.com/minad/corfu"&gt;Corfu&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Enhanced completion-at-point interface: get a fancy popup window to show completion candidates when completing a word in-buffer. Corfu uses a child frame by default; for non-GUI users there&amp;rsquo;s &lt;a href="https://codeberg.org/akib/emacs-corfu-terminal"&gt;corfu-terminal&lt;/a&gt; to make it work in TUI mode as well.&lt;/dd&gt;
&lt;dt&gt;&lt;a href="https://github.com/emacs-citar/citar"&gt;Citar&lt;/a&gt;&lt;/dt&gt;
&lt;dd&gt;Citations made easy: Citar reads &lt;code&gt;.bib&lt;/code&gt; databases and provides a slick completing-read interface to insert citation keys.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;There are &lt;em&gt;so&lt;/em&gt; many more packages that have come out in the past few years that I use all day every day. I wrote about some of them &lt;a href="https://lambdaland.org/posts/2024-05-30_top_emacs_packages/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="a-starter-kit-for-this"&gt;
  A starter kit for this
  &lt;a class="anchor" href="#a-starter-kit-for-this"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I made &lt;a href="https://codeberg.org/ashton314/emacs-bedrock"&gt;Emacs Bedrock&lt;/a&gt; as a starter kit to help explore all these nice new built-in features of Emacs. It&amp;rsquo;s a true starter kit: you download it once, then tweak it to your liking. By default it installs only one package (&lt;code&gt;which-key&lt;/code&gt;; Emacs 30 will have this package built-in) and the rest is tweaking some settings to be what I think the defaults &lt;em&gt;should&lt;/em&gt; be. It&amp;rsquo;s meant to encourage exploration. If you liked something from this post, take a look at Bedrock and maybe you&amp;rsquo;ll find something else there that you like!&lt;/p&gt;</description><author>Ashton Wiersdorf on Lambda Land</author><pubDate>Sat, 14 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2024-12-14_emacs_catchup/</guid></item><item><title>My deleted review of this restaurant</title><link>https://kinduff.com/2024/12/13/my-deleted-review-of-this-restaurant/</link><description>Guess my review didn't pass the community rules of Google Maps</description><author>Alejandro AR (kinduff)</author><pubDate>Fri, 13 Dec 2024 23:41:00 GMT</pubDate><guid isPermaLink="true">https://kinduff.com/2024/12/13/my-deleted-review-of-this-restaurant/</guid></item><item><title>Honolulu Hawaii: Well that’s off of Our Bucket List</title><link>https://digitalnomadder.micro.blog/2024/12/13/honolulu-hawaii-well.html</link><description>&lt;p&gt;We flew to Honolulu Hawaii last month.  We stayed at the &lt;a href="https://www.tripadvisor.com/Hotel_Review-g60982-d87016-Reviews-Hilton_Hawaiian_Village_Waikiki_Beach_Resort-Honolulu_Oahu_Hawaii.html"&gt;Hilton Hawaiian Village Waikiki Beach Resort&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It was overly touristy, a lot of shops and it just felt &lt;em&gt;old&lt;/em&gt;.  There was a small pool and it wasn’t like we were expecting.  We were expecting a more Caribbean feel or at least for it to feel like our own home in Florida.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://cdn.uploads.micro.blog/79953/2024/97f201fea9.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Traffic was horrible and getting around felt like driving in Atlanta.&lt;/p&gt;
&lt;p&gt;The hotel was at least free for us on points.&lt;/p&gt;
&lt;p&gt;We went to a food tour and it was the worse one we have done yet.&lt;/p&gt;
&lt;p&gt;The only saving grace was the luaha. Where the food was decent and they did put on a good show&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://cdn.uploads.micro.blog/79953/2024/e7b092a1d2.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;We were so over it that we ended up cancelling one of our plans.&lt;/p&gt;
&lt;p&gt;We had been talking about going to Hawaii since 2018, we were really disappointed.&lt;/p&gt;
&lt;p&gt;Maybe we went to the wrong island or it might have been our life is so much different now than then and we have done so much more that our expectations are different.&lt;/p&gt;</description><author>The Digital Nomad</author><pubDate>Fri, 13 Dec 2024 20:48:26 GMT</pubDate><guid isPermaLink="true">https://digitalnomadder.micro.blog/2024/12/13/honolulu-hawaii-well.html</guid></item><item><title>How to make your GitHub profile dynamic</title><link>https://www.patrickdap.com/post/make-github-profile-dynamic/</link><description>&lt;p&gt;&lt;em&gt;Optimizing the information you show in your GitHub profile with dynamic PRs and blog content&lt;/em&gt;&lt;/p&gt;&lt;img alt="How to make your GitHub profile dynamic" height="150" src="https://www.patrickdap.com/assets/patrickdappollonio-profile.BWzwsrmV_Z2ouP8d.jpg" style="float: right; margin: 0 0 12px 18px; border-radius: 8px;" width="150" /&gt;&lt;p&gt;Recently I was toying with the somewhat new GitHub feature that allows you to create a repository with the same name as your GitHub username and host in it a README.md file that then is displayed in your profile and visible to whoever visits your profile page. While this feature is good, it's not too dynamic, but there are two things in our favour: a) GitHub Actions are free for public repositories and b) we can use the GitHub API to fetch information about our repositories, pull requests and more. So I thought "why not make my GitHub profile more dynamic by adding my latest blog posts, my latest pull requests and my latest starred repositories to my profile?" and that's what I did, in fact, you can see it here! Here's how you can leverage the same workflow to achieve something very...&lt;/p&gt;&lt;div style="clear: both;"&gt;&lt;/div&gt;</description><author>Patrick D'appollonio</author><pubDate>Fri, 13 Dec 2024 07:56:53 GMT</pubDate><guid isPermaLink="true">https://www.patrickdap.com/post/make-github-profile-dynamic/</guid></item><item><title>Tinker Postman Collector Scribe</title><link>https://pncnmnp.github.io/blogs/national-postal-museum.html</link><description>In this blog post, we will explore art, USPS postal technology, philately, and generative art — all inspired by my trip to D.C. last month.</description><author>Parth Parikh's Blog</author><pubDate>Fri, 13 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://pncnmnp.github.io/blogs/national-postal-museum.html</guid></item><item><title>Cron jobs inside your web app</title><link>https://ricardoanderegg.com/posts/cron-jobs-inside-web-app-python/</link><description>&lt;p&gt;When you have a web application, almost always, you need to run some functions/scripts regularly. This what &lt;a href="https://en.wikipedia.org/wiki/Cron"&gt;cron&lt;/a&gt; is for. I wanted to find an easy way to run &amp;ldquo;cron-like&amp;rdquo; jobs inside a FastAPI app. The app runs inside docker, and I just need those jobs to run as long as the app is also running.&lt;/p&gt;
&lt;p&gt;Since the app is &amp;ldquo;async&amp;rdquo;, I found I can just have recurring background tasks as part of the app. We need to use the app &lt;a href="https://fastapi.tiangolo.com/advanced/events/"&gt;lifespan&lt;/a&gt; and some functions that run a task, the &lt;code&gt;asyncio.sleep&lt;/code&gt; for a pre-defined time. I decided to apply some random jitter to avoid multiple tasks starting at once. (Note: this can also be done in &amp;ldquo;sync&amp;rdquo; apps using threads).&lt;/p&gt;</description><author>Posts on rand[om]</author><pubDate>Fri, 13 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ricardoanderegg.com/posts/cron-jobs-inside-web-app-python/</guid></item><item><title>TIL: Zizmor and GitHub Actions security</title><link>https://blog.erethon.com/log/2024-12-08-gha-security/</link><description>I was reading up on the recent Ultralytics GitHub Action compromise and I stumbled upon this great analysis of the situation. In it, zizmor is introduced, which is a static analysis tool for GitHub Actions.
I experimented with it a bit and I have to say it's working great. It correctly identified misconfigured GitHub Actions on some repositories I was working on. It's another tool that's worth having as part of your CI.</description><author>Erethon's Corner</author><pubDate>Fri, 13 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.erethon.com/log/2024-12-08-gha-security/</guid></item><item><title>Luciatåget på Uppsala slott</title><link>https://liza.io/luciat%C3%A5get-p%C3%A5-uppsala-slott/</link><description>&lt;p&gt;Tidigare i kväll gick jag till Uppsala slott och träffade några kompisar för att se Luciatåget på slottets tak. Det var en kort föreställning, men så vackert! Den sista sången som de sjöng var helt magi.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Thu, 12 Dec 2024 23:02:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/luciat%C3%A5get-p%C3%A5-uppsala-slott/</guid></item><item><title>Personhood-based arguments on resuscitation of Extremely Premature Infants - practical philosophy seminar notes</title><link>https://liza.io/personhood-based-arguments-on-resuscitation-of-extremely-premature-infants-practical-philosophy-seminar-notes/</link><description>&lt;p&gt;This week&amp;rsquo;s practical philosophy seminar discussed personhood-based arguments for the resuscitation of extremely premature infants (EPIs). These notes focus less on discussion/paper specifics and more on what I learned about the topic as a whole during the discussion and afterward.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Thu, 12 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/personhood-based-arguments-on-resuscitation-of-extremely-premature-infants-practical-philosophy-seminar-notes/</guid></item><item><title>Green Goo (Republished)</title><link>https://denovo.substack.com/p/green-goo-republished</link><description>Synthetic biology, mirror life, and threats to global ecosystems</description><author>De Novo</author><pubDate>Thu, 12 Dec 2024 21:07:00 GMT</pubDate><guid isPermaLink="true">https://denovo.substack.com/p/green-goo-republished</guid></item><item><title>Humba II: Doubling Down on Deep Tech</title><link>https://www.codingvc.com/p/humba-ii-doubling-down-on-deep-tech</link><description>The Humba Ventures team is proud to announce the launch of Humba II, our oversubscribed $40M second fund focused on deep tech and American Dynamism.</description><author>Coding VC</author><pubDate>Thu, 12 Dec 2024 18:08:32 GMT</pubDate><guid isPermaLink="true">https://www.codingvc.com/p/humba-ii-doubling-down-on-deep-tech</guid></item><item><title>An Academic's Guide to Fribourg</title><link>https://bastian.rieck.me/blog/2024/fribourg/</link><description>&lt;p&gt;As &lt;a href="https://aidos.group"&gt;my research group&lt;/a&gt; starts to take root in
&lt;a href="https://en.wikipedia.org/wiki/Fribourg"&gt;Fribourg&lt;/a&gt;, I finally get to
take the time to note some things I like about the region and the
university. Much of this is inspired by &lt;a href="https://matt.might.net"&gt;Matt
Might&lt;/a&gt;, whose &lt;a href="https://matt.might.net/articles/tenure/"&gt;article on getting
tenure&lt;/a&gt; ends very poignantly:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;And come to the University of Utah!
Life is good here.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Without further ado, let me sing the praises of Fribourg and its environs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fribourg is a literal university town, with more than a quarter of
its inhabitants being students. In some sense, the city &lt;em&gt;is&lt;/em&gt; the
campus. Plus, it is a small town and highly walkable.&lt;/li&gt;
&lt;li&gt;The canton (i.e. the administrative unit, akin to a county) of
Fribourg is bilingual, with most inhabitants speaking German and
French as a native language. Since the city is so international,
everyone of course also speaks English.&lt;/li&gt;
&lt;li&gt;While Switzerland has higher costs of living than most countries,
Fribourg (both the city and the canton) offers affordable
accommodations and in general has lower costs of living.&lt;/li&gt;
&lt;li&gt;The whole region is well-connected, with major cities like
&lt;a href="https://en.wikipedia.org/wiki/Geneva"&gt;Geneva&lt;/a&gt; or
&lt;a href="https://en.wikipedia.org/wiki/Bern"&gt;Bern&lt;/a&gt; just a short train ride
away. As one of my colleagues so nicely put it: &amp;lsquo;Switzerland is so
small that almost all cities are suburbs of each other.&amp;rsquo;&lt;/li&gt;
&lt;li&gt;Switzerland in general sports cities with a high quality of life.
Whether you like the cosmopolitan nightlife or the solitude of
mountains, there is something for you.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;In terms of the university as such, we offer a large variety of
subjects, ranging from the humanities to the life sciences. While we are
not as big as our sibling universities &lt;a href="https://en.wikipedia.org/wiki/ETH_Zurich"&gt;ETH Zurich&lt;/a&gt;
or &lt;a href="https://en.wikipedia.org/wiki/%C3%89cole_Polytechnique_F%C3%A9d%C3%A9rale_de_Lausanne"&gt;EPFL&lt;/a&gt;,
we are doing very well. For machine learning (including deep learning)
and information retrieval/data mining, we are ranked &lt;a href="https://csrankings.org/#/index?mlmining&amp;amp;inforet&amp;amp;ch"&gt;3rd in Switzerland&lt;/a&gt;
and in the &lt;a href="https://csrankings.org/#/index?mlmining&amp;amp;inforet&amp;amp;europe"&gt;top 50 of all European universities&lt;/a&gt;.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Given our small size (just compare our faculty sizes to that of
other universities), that is quite a feat, in my opinion! Thus, I can
only echo Matt&amp;rsquo;s statements:&lt;/p&gt;
&lt;p&gt;Come to the University of Fribourg. Life is good here.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;When viewing these rankings, you might have to switch the region
of comparison manually, since CSRankings unfortunately has the
tendency to default to your current location.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Thu, 12 Dec 2024 16:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2024/fribourg/</guid></item><item><title>My take on Web Components</title><link>https://ricardoanderegg.com/posts/my-take-on-web-components/</link><description>&lt;p&gt;Lately, I&amp;rsquo;ve been trying to learn more JavaScript.&lt;/p&gt;
&lt;p&gt;I wanted to explore web components. After checking out some frameworks and tools, mainly &lt;a href="https://lit.dev/"&gt;lit&lt;/a&gt;, &lt;a href="https://tonicframework.dev/"&gt;tonic&lt;/a&gt; and &lt;a href="https://github.com/kgscialdone/facet"&gt;facet&lt;/a&gt;, I wanted to build my own thing.&lt;/p&gt;
&lt;p&gt;It ended up being heavily inspired by &lt;a href="https://tonicframework.dev/"&gt;tonic&lt;/a&gt;, but way simpler and with less features. I also really liked &lt;a href="https://github.com/lit/lit/tree/main/packages/lit-html"&gt;lit-html&lt;/a&gt;, which you can use as a separate package without having to use &lt;a href="https://lit.dev/"&gt;lit&lt;/a&gt;. It solves the problem of re-rendering only the UI parts that have changed, and it&amp;rsquo;s awesome at doing it.&lt;/p&gt;</description><author>Posts on rand[om]</author><pubDate>Thu, 12 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ricardoanderegg.com/posts/my-take-on-web-components/</guid></item><item><title>Learning to Type Fast</title><link>/typing-fast/</link><description>&lt;p&gt;Typing is the conduit by which thoughts flow via the keyboard to the computer. Doing this quickly can massively improve not only your productivity but your flow.
Remember when you had a phone that looked like this?&lt;/p&gt;
&lt;div class="image"&gt;
	&lt;img alt="A keyboard with number 0-9. Each button represents 3 characters" src="../../assets/images/old-keyboard.jpg" /&gt;
&lt;/div&gt;
&lt;p&gt;Remember how slow it was? It took ages to write something - 'wic iz y we rote lyk this'.&lt;/p&gt;
&lt;p&gt;If you work as a software engineer, you'll be typing all day - whether programming or more predominantly, emails and slack messages.&lt;/p&gt;
&lt;p&gt;I found that I was typing reasonably fast (70WPM) but often found myself being unable to keep up with my thoughts as I typed. And I made a LOT of mistakes.
The reason? I typed with my index fingers only. I was super quick with it but I knew that I would never be able to write faster unless I retaught myself to write with all 10 digits.&lt;/p&gt;
&lt;p&gt;My first scope was to decide what my baseline was - a test that I could use to track my typing speed over the course of this learning experience. For me this was the standard 1 minute typing test on 10fastfingers.&lt;/p&gt;
&lt;p&gt;Next, I researched a tool that could re-teach me how to type. I found typingclub to be an incredible tool for this.&lt;/p&gt;
&lt;h2&gt;The build up&lt;/h2&gt;
&lt;p&gt;Typingclub starts by building you up what fingers you use. Initially it just focuses on the index finger and builds the muscle memory for what keys it should type. It keeps things simple by having everything lower case and focusing just on the position of your fingers first before typing anything. Once I had reached a stage where I could use 3 fingers I started to apply this in my job. My typing was biblically slow but I was building the muscle memory.&lt;/p&gt;
&lt;h2&gt;A new keyboard&lt;/h2&gt;
&lt;div class="image"&gt;
	&lt;img src="../../assets/images/my-keyboard.jpg" /&gt;
&lt;/div&gt;
&lt;p&gt;This has nothing to do with typing speed per se (more on that after), but a new keyboard would affect my typing experience.
Previously, I was using a Logitech K380. A fantastic keyboard with multi-bluetooth and plenty of action keys. For £30, it's a steal and I've owned two of them over the years (after the first one met it's unfortunate end with a &amp;quot;e&amp;quot; key being totally broken). But I found the typing experience quite flat.
A mechanical keyboard was the obvious answer. But this posed a problem. When I was a teenager, I used to play copious amounts of starcraft 2 - with a mechanical keyboard. Soon, my left forearm swelled with fluid and I eventually had to get carpal tunnel surgery to correct the problem.
Wanting to avoid all that pain again (literally), I was a bit hesitant about picking up a new one. Fortunately, mechanical keyboards have come a long way. And one of those new developments is a &amp;quot;low profile&amp;quot; mechanical keyboard. They are a slimmed down version of mechanical keyboards that are less raised so don't strain your hands as much.&lt;/p&gt;
&lt;p&gt;After a great deal of searching culminating in an excel spreadsheet comparison, I landed on the Nuphy Air75v2 with Wisteria switches. These switches are most akin to cherry brown switches and have a nice tactile bump on each keystroke. Crucially, they have multi-bluetooth to allow switching between my work and personal machine.&lt;/p&gt;
&lt;p&gt;The new keyboard actually reduced my typing speed by a lot. Because I kept accidentally hitting keys or not pressing the correct one entirely.
It was worth the persistence though because the keyboard massively improved my enjoyment of typing. The satisfying &amp;quot;thonk&amp;quot; with each keypress echoing like a sturdy industrial-era engine.&lt;/p&gt;
&lt;h2&gt;More keys&lt;/h2&gt;
&lt;p&gt;Last up was my pinky finger. I found this difficult to practise because &amp;quot;non-pinky&amp;quot; way of typing was so heavily ingrained. With practise and being conscious about it, I managed it. Although, I'll confess that I'm far from perfect in this regard.
I kept grinding on these lessons daily - usually doing 5 a day. They soon moved onto more complex topics such as punctuation, numbers and macros. I'm continuing these lessons now but by around lesson 300 I had a fairly good typing speed.&lt;/p&gt;
&lt;p&gt;Once I had grasped using all 10 fingers, I moved on to measuring my progress and practising more common phrases. For this I used, 10fastfingers.com and the top 250 words test. I applied the 80/20 rule in this regard. My thinking was that if I could type the most used 250 words then this would account for 80% of my typing.&lt;/p&gt;
&lt;h2&gt;Progress&lt;/h2&gt;
&lt;div class="image"&gt;
	&lt;img src="../../assets/images/typing-progress.jpg" /&gt;
&lt;/div&gt;
My highest words per minute (as measured on 10fastfingers) is 81WPM and my average is staying well above where I started so I'm count that as a success!
&lt;p&gt;If you work on a computer daily, I'd strongly recommend improving your typing speed as your fluency of transmitting information will increase dramatically.&lt;/p&gt;</description><author/><pubDate>Thu, 12 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">/typing-fast/</guid></item><item><title>Tim Ferriss explains how identifying “super tasks” in his to-do list helps plan his day</title><link>https://bill.harding.blog/2024/12/11/tim-ferriss-explains-how-identifying-super-tasks-in-his-to-do-list-helps-plan-his-day/</link><description/><author>Relentless Simplicity</author><pubDate>Thu, 12 Dec 2024 01:14:03 GMT</pubDate><guid isPermaLink="true">https://bill.harding.blog/2024/12/11/tim-ferriss-explains-how-identifying-super-tasks-in-his-to-do-list-helps-plan-his-day/</guid></item><item><title>Julbord! Jag började äta fisk igen i precis rätt tid.</title><link>https://liza.io/julbord-jag-b%C3%B6rjade-%C3%A4ta-fisk-igen-i-precis-r%C3%A4tt-tid./</link><description>&lt;p&gt;Igår gick vi på vårt team-julbord! Jag har varit vegetarian i många, många år. Jag hade inte ätit fisk på ungefär&amp;hellip; 10 år kanske, innan tidigare i år. Så detta var mitt första julbord (som jag minns) där jag åt fisk, och det var så väldigt gott! Men jag åt för mycket av det och vaknade upp med ont i magen. Värt det.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Wed, 11 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/julbord-jag-b%C3%B6rjade-%C3%A4ta-fisk-igen-i-precis-r%C3%A4tt-tid./</guid></item><item><title>Reviewing A Deepness in the Sky by Vernor Vinge</title><link>https://davi.sh/reading/2024/a-deepness-in-the-sky/</link><description>&lt;p&gt;This was one of the most creative and enjoyable sci-fi books I’d read in a while. The two
main storylines are split between spider-like intelligent aliens on a remote planet and
the conflict between humans in orbit who are preparing to make First Contact.&lt;/p&gt;
&lt;p&gt;The way that the alien storyline is “translated” into English is a very compelling
storytelling device. The human storyline paints a vivid picture of a far-future
spacefaring civilization that still feels grounded by the limitations of
technology. Spaceflight takes decades in ramjet ships, with crews rotating in and out of
cryosleep. I loved the ways that cryosleep affected the political and dynamics among the
humans.&lt;/p&gt;
&lt;p&gt;The Qeng Ho trading civilization is post-Cold War neoliberalism taken to its limit. &lt;em&gt;A
Deepness in the Sky&lt;/em&gt; was written in the late 90s and the Qeng Ho are cast as the heroes of
the story, but Vinge still manages to describe and portray the downfalls and limits of the
unending quest for efficiency that felt precient when reading after the supply chain
disruptions of the 2020 pandemic.&lt;/p&gt;
&lt;p&gt;Vinge was a Computer Science professor as his day job. One of the most interesting ideas
he puts forward in the book is that of the programmer-archaeologist. Thousands of years
into the digital age, all useful programs have already been written by someone,
somewhere. The job of the programmer isn’t to write new code, but to sift through existing
archives to find what’s useful in the current moment.&lt;/p&gt;
&lt;p&gt;Overall, a really enjoyable book that continually made me stop and think.&lt;/p&gt;</description><author>Davis Haupt's Blog</author><pubDate>Wed, 11 Dec 2024 19:00:00 GMT</pubDate><guid isPermaLink="true">https://davi.sh/reading/2024/a-deepness-in-the-sky/</guid></item><item><title>Ad sharks</title><link>https://paulw.tokyo/ad-sharks/</link><description>&lt;p&gt;I don't own a TV, but there's one at my gym. This morning, there was this... infomercial? I don't know how to call it, it lasted at least an hour and had the tone of the usual off-hour Japanese TV. Programs that try, not very hard, to be both entertainment and journalistic news. Of course it was interspaced by plenty of ads.&lt;/p&gt;
&lt;p&gt;The topic was shark fishing, and how eating them keeps you young. And all the commercials, which seemed to feature only one brand, were dietary supplements made from shark extracts.&lt;/p&gt;
&lt;p&gt;Both the show and the ads clearly targeted the elderly, with bold claims from "scientists" in lab coats of rejuvenating by 15 years.&lt;/p&gt;
&lt;p&gt;No-one likes ads but this felts particularly egregious. It triggered me somehow.&lt;/p&gt;
&lt;p&gt;Was it the visual impact of fishing large animals just to put them in pills? The certainty that my parents, not getting any younger, will be targeted by those scams? The non-existent boundary between content and adverts?&lt;/p&gt;
&lt;p&gt;How do we stop this?&lt;/p&gt;
&lt;p&gt;Not just TV scams preying on the elderly, the creep of ads for everything everywhere.&lt;/p&gt;
&lt;p&gt;All growing tech companies focus on ads, even Apple now. It just seems so pervasive.&lt;/p&gt;
&lt;p&gt;I don't think we can fight profits, we need to redesign our society so that ads aren't front and center.&lt;/p&gt;
&lt;p&gt;How?&lt;/p&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Buy my new book for answers!&lt;/em&gt;&lt;/p&gt;</description><author>paulw.tokyo</author><pubDate>Wed, 11 Dec 2024 17:24:56 GMT</pubDate><guid isPermaLink="true">https://paulw.tokyo/ad-sharks/</guid></item><item><title>Real-time semantic search demo</title><link>https://paulw.tokyo/real-time-semantic-search-demo/</link><description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;TLDR&lt;/em&gt;&lt;/strong&gt;&lt;em&gt;: I wanted to make a fast and low complexity "search engine", and got good results with neural network embeddings combined with an approximate vector search.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Recent advances in large deep learning models have brought interesting multi-modal capabilities, notably for combined text and images, like &lt;a href="https://imagen.research.google/"&gt;Imagen&lt;/a&gt; and &lt;a href="https://openai.com/dall-e-2/"&gt;DALL-E 2&lt;/a&gt; for image synthesis from raw text. &lt;a href="https://openai.com/blog/clip/"&gt;CLIP&lt;/a&gt;, which is also used in DALL-E for image ranking, allow projecting both text and images into a coherent space.&lt;/p&gt;
&lt;p&gt;I want to be able to search images from text, so using CLIP we can project both into the same space, making them comparable. Concretely we obtain vectors, and for pairs of vectors we can take their norm as a distance to find closest ones, and return that as a search result.&lt;/p&gt;
&lt;p&gt;In practice comparing one vector (example our query) to all existing ones could be very slow if done exactly. Luckily &lt;em&gt;approximate&lt;/em&gt; nearest neighbor (ANN) methods allow us to trade-off a tiny bit of accuracy for massive speed-ups and memory savings. Lots of methods exist (&lt;a href="http://ann-benchmarks.com/"&gt;here for benchmarks&lt;/a&gt; and &lt;a href="https://www.pinecone.io/learn/"&gt;pinecone&lt;/a&gt; has great docs as an introduction), including innovative &lt;a href="https://arxiv.org/abs/2110.05789"&gt;learned quantization methods&lt;/a&gt; that achieve excellent compression.&lt;/p&gt;
&lt;p&gt;For the user interaction, any web tech stack can do the job, I went with &lt;a href="https://fastapi.tiangolo.com/"&gt;FastAPI&lt;/a&gt; for the backend and &lt;a href="https://svelte.dev/"&gt;Svelte&lt;/a&gt; for the frontend.&lt;/p&gt;
&lt;p&gt;So let's combine the three!&lt;/p&gt;

&lt;center&gt;
&lt;video controls="controls" loop="loop" preload="preload"&gt;
&lt;source src="https://bear-images.sfo2.cdn.digitaloceanspaces.com/m3at/ann_demo_long_full.mp4" type="video/mp4" /&gt;
&lt;/video&gt;
&lt;/center&gt;
&lt;p&gt;The demo above search over ~1.5M images, taking about 3Gb of space for the index and with latency under 100ms. This is running on single process from my laptop.&lt;/p&gt;
&lt;p&gt;To show off the speed, I prepared a tiny version of all images (&lt;500 bytes) that are returned with the search results and loaded immediately. The full size image is then loaded asynchronously, that can take up to a second as those are random internet images.&lt;/p&gt;
&lt;p&gt;Also human typing speed is not that fast, so to make the search field usable I added a delay before triggering the search, so the latency you see is after the search query is sent.&lt;/p&gt;
&lt;center&gt;
&lt;video controls="controls" loop="loop" preload="preload"&gt;
&lt;source src="https://bear-images.sfo2.cdn.digitaloceanspaces.com/m3at/ann_demo_short_fast.mp4" type="video/mp4" /&gt;
&lt;/video&gt;
&lt;/center&gt;
&lt;p&gt;Here is an other demo, with full image loading disabled and a lower typing delay, just to show the speed.&lt;/p&gt;
&lt;p&gt;I'm quite pleased with those results, there's still a lot that could be done to speed-up the search further but it's fast enough for a prototype. The index size/accuracy ratio could also be improved a lot, and the CLIP model should be transferable into a smaller inference network. Also I could make a UI that doesn't suck 😬&lt;/p&gt;
&lt;p&gt;Calling this a "search engine" is a bit of a stretch, there is limited control and no filtering options but the tech stack is very simple (it only took a day to make this demo!). I think simplicity has value in itself. Though with the &lt;a href="https://github.com/elastic/elasticsearch/pull/84734"&gt;recent support of ANN+filtering&lt;/a&gt; in Lucene/Elasticsearch the comparison would be interesting.&lt;/p&gt;</description><author>paulw.tokyo</author><pubDate>Wed, 11 Dec 2024 17:24:50 GMT</pubDate><guid isPermaLink="true">https://paulw.tokyo/real-time-semantic-search-demo/</guid></item><item><title>Denormal number at inference in PyTorch</title><link>https://paulw.tokyo/denormal-number-at-inference-in-pytorch/</link><description>&lt;p&gt;The other day at work I noticed a &lt;strong&gt;slowdown in runtime&lt;/strong&gt; between a model with random weights compared to tuned ones.&lt;/p&gt;
&lt;p&gt;It turned out to be due to &lt;a href="https://en.m.wikipedia.org/wiki/Subnormal_number"&gt;denormal numbers&lt;/a&gt; computation on the cpu being much slower than the normal arithmetic. Denormal numbers are very low magnitude floats, treated differently to keep precision.&lt;/p&gt;
&lt;p&gt;For deep learning, those are largely below significance and can be flushed to zero without accuracy loss. To do so for example in PyTorch, either set &lt;a href="https://pytorch.org/docs/stable/generated/torch.set_flush_denormal.html" title="https://pytorch.org/docs/stable/generated/torch.set_flush_denormal.html"&gt;the appropriate flag&lt;/a&gt;: &lt;code&gt;torch.set_flush_denormal(True)&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Or manually round down your weights, I choose &lt;math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;mrow&gt;&lt;mn&gt;10&lt;/mn&gt;&lt;mi&gt;e&lt;/mi&gt;&lt;mo&gt;&amp;#x02212;&lt;/mo&gt;&lt;mn&gt;12&lt;/mn&gt;&lt;/mrow&gt;&lt;/math&gt; arbitrarily:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;torch&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;1e-12&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;

&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load_state_dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;torch&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;./model.pth&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The speedup will be CPU and model dependent, for me it was quite significant (x2!). Hopefully this tip will save you some inference time too.&lt;/p&gt;</description><author>paulw.tokyo</author><pubDate>Wed, 11 Dec 2024 17:24:44 GMT</pubDate><guid isPermaLink="true">https://paulw.tokyo/denormal-number-at-inference-in-pytorch/</guid></item><item><title>Space filling curves and CNN</title><link>https://paulw.tokyo/space-filling-curves-and-cnn/</link><description>&lt;p&gt;👷🏻 this is a work in progress 🔧&lt;/p&gt;
&lt;p&gt;Sometimes by exploring incongruous ideas, one can make interesting discoveries. This is known as &lt;a href="https://en.wikipedia.org/wiki/Serendipity"&gt;serendipity&lt;/a&gt; in science, with penicillin being a famous example of an unplanned discovery.&lt;/p&gt;
&lt;p&gt;What I experimented with this week-end… is no such case. 😅&lt;br /&gt;
In the spirit of not holding back negative results though, and for the unlikely possibility that it turns out helpful for someone, I'll share it regardless!&lt;/p&gt;
&lt;h1 id="too-many-dimensions"&gt;Too many dimensions&lt;/h1&gt;&lt;p&gt;In computer vision we mostly deal with spatial data, so to make our life easier we often bake into the models the assumption that nearby elements are related. This is what the fundamental convolution operation give us in deep learning models, some bias towards spatial locality.&lt;/p&gt;
&lt;p&gt;However not everything can be applied spatially, particularly on high dimensional spaces. For examples, building blocks designed for natural language processing often consider only one dimension. Even the &lt;a href="https://arxiv.org/abs/1706.03762"&gt;transformer model&lt;/a&gt;, which is position independent in it's original formulation, has been the focus of numerous researches attempting to &lt;a href="https://ai.googleblog.com/2020/01/reformer-efficient-transformer.html"&gt;bring back some locality&lt;/a&gt;, notably for efficiency improvements. Wouldn't it be great to be able to use, as is, techniques developed for &lt;math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;mrow&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mi&gt;D&lt;/mi&gt;&lt;/mrow&gt;&lt;/math&gt; space on arbitrarily high &lt;math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;mi&gt;D&lt;/mi&gt;&lt;/mrow&gt;&lt;/math&gt;?&lt;/p&gt;
&lt;p&gt;The straightforward way to achieve it is to flatten whatever dimensions we have in order. For example, with a &lt;math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;mrow&gt;&lt;mn&gt;2&lt;/mn&gt;&lt;mi&gt;D&lt;/mi&gt;&lt;/mrow&gt;&lt;/math&gt; image we could take each row and concatenate them into a big list, and we're done!&lt;br /&gt;
The issue is that we will break spatial locality. Neighboring pixels in a row will stay together, but pixel that were close within a column will suddenly have arbitrarily big distances separating them. Instead, we would like to flatten space while keeping the locality property as best as we can.&lt;/p&gt;
&lt;h1 id="space-filling-curves-to-the-rescue"&gt;Space-filling curves to the rescue&lt;/h1&gt;&lt;p&gt;Luckily, mathematicians have been &lt;a href="https://en.wikipedia.org/wiki/Space-filling_curve" title="Space-filling curve"&gt;studying this problem&lt;/a&gt;! I recommend this &lt;a href="https://www.youtube.com/watch?v=3s7h2MHQtxc" title="Hilbert&amp;apos;s Curve: Is infinite math useful?"&gt;3Blue1Brown&lt;/a&gt; for a great visual introduction, but in short the idea is to fill the entirety of space with a single curve.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Four iterations of the Z-order curve" src="/uploads/four-level_z.svg" title="Four iterations of the Z-order curve" /&gt;&lt;/p&gt;
&lt;p&gt;There are a few different functions available, like the Peano and Hilbert curves, or most appropriate for us the &lt;a href="https://en.wikipedia.org/wiki/Z-order_curve"&gt;Z-order curve&lt;/a&gt; (also known as Morton code). The advantage of the later is that, as illustrated in the figure above, on top of filling space it also preserve locality. Well, to an extent at least.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Example of indexing using Z-order curve" src="/uploads/example_z_order_mapping.webp" title="Example of indexing using Z-order curve" /&gt;&lt;/p&gt;
&lt;p&gt;Using the Z-order function, we can create an index and use it for mapping from &lt;math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;mrow&gt;&lt;mi&gt;N&lt;/mi&gt;&lt;mi&gt;D&lt;/mi&gt;&lt;/mrow&gt;&lt;/math&gt; to &lt;math display="inline" xmlns="http://www.w3.org/1998/Math/MathML"&gt;&lt;mrow&gt;&lt;mn&gt;1&lt;/mn&gt;&lt;mi&gt;D&lt;/mi&gt;&lt;/mrow&gt;&lt;/math&gt;. Using &lt;a href="https://github.com/arogozhnikov/einops"&gt;einops&lt;/a&gt; to avoid getting lost in the dimensions, it is easy to create a function doing it for PyTorch.&lt;/p&gt;
&lt;p&gt;(TODO: clean-up and publish the code)&lt;/p&gt;
&lt;p&gt;Now that we have a way to flatten space while keeping some locality, we can design an experiment! To keep things fast I used the small CIFAR-10 dataset, which is fast enough to train on, even with a naive training loop implementation. As a bonus, I already had some code lying around that allowed me to reach 3000 samples/second on my aging 1070Ti (loosely based on &lt;a href="https://x.com/dcpage3/status/1163563850442182657"&gt;Myrtle.ai&lt;/a&gt; posts about training speedup; a story for another time).&lt;/p&gt;
&lt;p&gt;I tried 3 configurations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A baseline model with the usual 2D convolutions&lt;/li&gt;
&lt;li&gt;A model using 1D convolutions after flattening the input using the Z-order function&lt;/li&gt;
&lt;li&gt;The same 1D model but with naive flattening, and random flattening&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All models are kept as close as possible, using the same 9-layers architecture and swapping the convolutions/batch-norm/pooling to either 2D or 1D.&lt;br /&gt;
Because convolving on lower dimensions use smaller kernels it result in less parameters, so I compensate by adjusting the layers' width until the number of trainable parameters match. While this is by no mean sufficient to make it an apple to apple comparison, it will have to do!&lt;/p&gt;
&lt;p&gt;(TODO: finish the write-up :D)&lt;/p&gt;</description><author>paulw.tokyo</author><pubDate>Wed, 11 Dec 2024 17:24:38 GMT</pubDate><guid isPermaLink="true">https://paulw.tokyo/space-filling-curves-and-cnn/</guid></item><item><title>Creating a static blog, the convoluted way</title><link>https://paulw.tokyo/creating-a-static-blog-the-convoluted-way/</link><description>&lt;p&gt;&lt;em&gt;TLDR: I wanted to create a static blog while being able to type articles from a nice UI, from anywhere. It is quite straightforward with&lt;/em&gt; &lt;a href="https://forestry.io/"&gt;&lt;em&gt;forestry&lt;/em&gt;&lt;/a&gt; &lt;em&gt;+&lt;/em&gt; &lt;a href="https://gohugo.io/"&gt;&lt;em&gt;hugo&lt;/em&gt;&lt;/a&gt; &lt;em&gt;+&lt;/em&gt; &lt;a href="https://pages.github.com/"&gt;&lt;em&gt;github pages&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt; &lt;a href="#tldr-jump"&gt;Jump&lt;/a&gt; to the solution.&lt;/p&gt;
&lt;h2 id="motivation"&gt;Motivation&lt;/h2&gt;&lt;p&gt;If you're looking to create a blog, you're in luck! There are countless tools to help you get up and running in a few minutes. For example &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; will generate a static site from a few markdown files, and pushing it to github in a repository named &lt;code&gt;YOUR_USERNAME.github.io&lt;/code&gt; will put you online.&lt;/p&gt;
&lt;p&gt;That's it! You're done! ✨&lt;/p&gt;
&lt;p&gt;However if it's too easy it isn't fun. Also by accepting what's good enough, you might actually write &lt;em&gt;content&lt;/em&gt; into your blog! Let's not do that.&lt;/p&gt;
&lt;p&gt;Here are my requirements:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The website should be &lt;strong&gt;static&lt;/strong&gt;. It makes hosting cheap or even free.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Modifying any parts&lt;/strong&gt; should be possible. I want control of my site.&lt;/li&gt;
&lt;li&gt;Once set-up, I should be able to &lt;strong&gt;focus on the content&lt;/strong&gt;. No distraction from the command line, just press save and be done.&lt;/li&gt;
&lt;li&gt;Be able to &lt;strong&gt;edit from anywhere&lt;/strong&gt;. That typo your friend noticed? Fix it with a quick edit from your phone!&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The first two can be satisfied by any static site generator. The third can be solved by a nice tool like &lt;a href="https://getpublii.com/"&gt;Publii&lt;/a&gt;, though it does make customizing details a bit more tricky. Editing from anywhere is easy with commercial offerings like &lt;a href="https://wordpress.com/"&gt;WordPress&lt;/a&gt;, but the website is not static and I would need to give up a lot of control.&lt;/p&gt;
&lt;p&gt;The solution for all four come from static &lt;strong&gt;content management system&lt;/strong&gt; (CMS), which –as the name imply– will provide tools to manage your content and generate your static site.&lt;/p&gt;
&lt;p&gt;I found two candidates: &lt;a href="https://www.netlifycms.org/"&gt;NetlifyCMS&lt;/a&gt; and &lt;a href="https://forestry.io/"&gt;Forestry.io&lt;/a&gt;. Both are promising and let you work with a site generator of your choosing. However I need only one, and forestry's editor looked prettier 🤩&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution&lt;/h2&gt;&lt;p&gt;&lt;a name="tldr-jump"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you want to look at the code directly, you can find it &lt;a href="https://github.com/m3at/blog_generator"&gt;here&lt;/a&gt;. The steps below explain how to get there.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With the context out of the way, here are the necessary steps:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a github repository matching your username, for example &lt;code&gt;jondoe.github.io&lt;/code&gt; (if you're not familiar with git or github, head &lt;a href="https://pages.github.com/"&gt;over there&lt;/a&gt; for some simple explanations). Once done &lt;code&gt;git clone https://github.com/jondoe/jondoe.github.io&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Install Hugo and follow the &lt;a href="https://gohugo.io/getting-started/quick-start/"&gt;quick start guide&lt;/a&gt;. Now is a good time to &lt;a href="https://themes.gohugo.io/"&gt;choose a theme&lt;/a&gt;. Push the code Hugo created for you into a new repository, for example &lt;code&gt;https://github.com/&lt;/code&gt;&lt;em&gt;&lt;code&gt;jondoe&lt;/code&gt;&lt;/em&gt;&lt;code&gt;/site_generator&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Once you're done configuring your site to your liking, copy the generated static site (under &lt;code&gt;./public&lt;/code&gt;) to your &lt;code&gt;jondoe.github.io&lt;/code&gt; directory. Push, and admire your website at &lt;code&gt;https://jondoe.github.io&lt;/code&gt;!&lt;/li&gt;
&lt;li&gt;Now we can setup the CMS. Login with your github account into forestry.io and follow the &lt;a href="https://forestry.io/docs/quickstart/setup-site/"&gt;quickstart guide&lt;/a&gt;. (I know, I said that for Hugo already, that's the last one I promise!). You will want to link the &lt;code&gt;site_generator&lt;/code&gt; repo, not the one hosting your generated site itself. Now from forestry you will be able to write new content, however it's not published to github pages yet.&lt;/li&gt;
&lt;li&gt;To automate the publishing, we will use github actions, that will allow us to push the changes to the static site into the right repository. Luckily some nice folks already did the job for us, so let's use the &lt;a href="https://github.com/peaceiris/actions-hugo"&gt;example they provide&lt;/a&gt;. There are multiple ways to authenticate from your script, suggest using the &lt;a href="https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-create-ssh-deploy-key"&gt;deploy key&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's it! (again)&lt;/p&gt;
&lt;p&gt;Now you can edit content from forestry, and upon saving your changes will be published automatically. After a blog post or two, internet fame should follow!&lt;/p&gt;</description><author>paulw.tokyo</author><pubDate>Wed, 11 Dec 2024 17:24:31 GMT</pubDate><guid isPermaLink="true">https://paulw.tokyo/creating-a-static-blog-the-convoluted-way/</guid></item><item><title>Basic watermark removal in videos</title><link>https://paulw.tokyo/basic-watermark-removal-in-videos/</link><description>&lt;p&gt;The general problem of completing images and videos depending on a mask is called &lt;a href="https://en.wikipedia.org/wiki/Inpainting"&gt;inpainting&lt;/a&gt;, and numerous methods exists to tackle this problem in &lt;a href="https://dmitryulyanov.github.io/deep_image_prior" title="deep image prior"&gt;ingenuous ways&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;However most approaches are relatively costly to run, especially without a graphic card, so I wanted to see what result we could get with simple and fast methods.&lt;/p&gt;
&lt;p&gt;You can find the code for the steps below in &lt;a href="https://github.com/m3at/video-watermark-removal" title="github video-watermark-removal"&gt;this repository&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Watermark removal on one frame" src="https://bear-images.sfo2.cdn.digitaloceanspaces.com/m3at/watermark_removal.webp" title="Example watermark removal" /&gt;&lt;/p&gt;
&lt;p&gt;To start, we need to find the mask, which correspond to the location of pixels to remove.&lt;/p&gt;
&lt;p&gt;Using &lt;a href="https://www.ffmpeg.org/" title="ffmpeg website"&gt;ffmpeg&lt;/a&gt; we can extract the timestamps of the &lt;a href="https://en.wikipedia.org/wiki/Key_frame" title="key frame"&gt;key frames&lt;/a&gt; in the video. Getting only the timing is fast, and we can later cap to a maximum the number of frames to actually extract. We could also take random frames, but the key frames are more likely to be diverse (can't be too close in time), and faster to extract as other frames need to be reconstructed from the closest key.&lt;/p&gt;
&lt;p&gt;With ffmpeg, we just need to run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ffprobe&lt;span class="w"&gt; &lt;/span&gt;-select_streams&lt;span class="w"&gt; &lt;/span&gt;v&lt;span class="w"&gt; &lt;/span&gt;-skip_frame&lt;span class="w"&gt; &lt;/span&gt;nokey&lt;span class="w"&gt; &lt;/span&gt;-show_frames&lt;span class="w"&gt; &lt;/span&gt;-show_entries&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pkt_pts_time&lt;span class="w"&gt; &lt;/span&gt;video.mp4
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then for each &lt;code&gt;TIMESTAMP&lt;/code&gt; obtained, we can extract the frame:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ffmpeg&lt;span class="w"&gt; &lt;/span&gt;-ss&lt;span class="w"&gt; &lt;/span&gt;TIMESTAMP&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;video.mp4&lt;span class="w"&gt; &lt;/span&gt;-vframes&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;frame.png
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And lastly we can aggregate the results of a simple image filter over all frames, to create a mask:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Compute the gradients per image&lt;/span&gt;
&lt;span class="n"&gt;dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gradient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;images&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Average globally&lt;/span&gt;
&lt;span class="n"&gt;mean_dx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;mean_dy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="c1"&gt;# Filter on both axis at a hand picked threshold&lt;/span&gt;
&lt;span class="n"&gt;threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;salient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;mean_dx&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mean_dy&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;salient&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;normalize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gaussian_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;salient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sigma&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;salient&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uint8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Bonus, if one wants to do it without python, it should be doable using ImageMagick's &lt;code&gt;convert&lt;/code&gt; with the existing&lt;/em&gt; &lt;a href="https://legacy.imagemagick.org/Usage/convolve/#sobel" title="Sobel ImageMagick"&gt;&lt;em&gt;Sobel filter&lt;/em&gt;&lt;/a&gt; &lt;em&gt;and &lt;code&gt;-evaluate-sequence mean *.png mean.png&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We now have a global mask to inpaint, so we can simply use ffmpeg's &lt;a href="https://ffmpeg.org/ffmpeg-filters.html#removelogo"&gt;&lt;code&gt;removelogo&lt;/code&gt;&lt;/a&gt; filter to obtain our cleaned-up video:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;ffmpeg&lt;span class="w"&gt; &lt;/span&gt;-i&lt;span class="w"&gt; &lt;/span&gt;video.mp4&lt;span class="w"&gt; &lt;/span&gt;-vf&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;removelogo=mask.png&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;cleaned.mp4
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This run at around x3 real-time on my aging MacBook Pro (i5-5287U), with a fixed overhead of ~5s for the mask extraction, which is fast! ✨&lt;/p&gt;
&lt;p&gt;Clearly the resulting inpainting is not of high quality –we're not even leveraging temporal information across frames– but is reasonable enough as a baseline to compare against real-time approaches.&lt;/p&gt;</description><author>paulw.tokyo</author><pubDate>Wed, 11 Dec 2024 17:23:38 GMT</pubDate><guid isPermaLink="true">https://paulw.tokyo/basic-watermark-removal-in-videos/</guid></item><item><title>Mostly Stakeless</title><link>https://bastian.rieck.me/blog/2024/stakeless/</link><description>&lt;p&gt;One of the social conventions I still have trouble understanding is the
tacit requirement to have opinions on &lt;em&gt;everything&lt;/em&gt;, in particular
complex socio-political conflicts. While I am not apolitical in the
sense that I &lt;em&gt;do&lt;/em&gt; have political opinions on some issues, I feel no need
to know the relevant talking points or assume a position in &lt;em&gt;every&lt;/em&gt;
conflict. I also do not feel the urge to discuss or state my political
views publicly because I am just some random person and cannot
possibly contribute anything useful.
This is not your standard case of &lt;a href="https://www.lesswrong.com/posts/9weLK2AJ9JEt2Tt8f/politics-is-the-mind-killer"&gt;politics is the mind-killer&lt;/a&gt;,&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;
but a protection against my tendency to become &lt;a href="https://en.wikipedia.org/wiki/Ne_supra_crepidam"&gt;ultracrepidarian&lt;/a&gt;,&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;
especially when I lack knowledge of the issue at hand.&lt;/p&gt;
&lt;p&gt;For instance, I find it hard to form substantiated opinions on global conflicts in
which I do not have a direct &amp;lsquo;stake.&amp;rsquo; I am empathising with those that
are displaced, wounded, or even killed in such conflicts, but I find it
hard to think about such conflicts in terms of right or wrong, when,
historically speaking, &amp;rsquo;everyone sucks here&amp;rsquo; would be a smarter position
to take. However, since I have not been trained as a historian and/or as
a political scientist, I often do not know who the &amp;lsquo;good guys&amp;rsquo; are.&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;
In some conflicts&amp;mdash;the recent &lt;a href="https://en.wikipedia.org/wiki/Fall_of_the_Assad_regime"&gt;fall of the Assad
regime&lt;/a&gt; comes to
mind&amp;mdash;it even seems to me that a devil has been replaced by some demons
and I honestly do not know where things are going&amp;hellip;&lt;/p&gt;
&lt;p&gt;Ever the optimist, I hope for good to prevail in the world, and I find
some solace in the words of &lt;a href="https://en.wikipedia.org/wiki/Maimonides"&gt;Maimonides&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Men frequently think that the evils in the world are more numerous
than the good things; many sayings and songs of the nations dwell on
this idea. [&amp;hellip;] Not only common people make this mistake, but even
many who think they are wise.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Much to my chagrin, though, there is a tendency of people in social
media to make assumptions about my political views or, in some cases,
&lt;a href="https://admonymous.co/pseudomanifold"&gt;chiding me anonymously&lt;/a&gt; for not
taking their preferred stance. While I appreciate the feedback I receive
anonymously, I want to gently caution people: Please do not assume that
I do not care. Having no stake in most conflicts, I just do not want to
add to the noise and I would rather prefer a nuanced discussion.&lt;/p&gt;
&lt;p&gt;I feel reminded of a running gag in &lt;a href="https://en.wikipedia.org/wiki/Terry_Pratchett"&gt;Terry Pratchett&lt;/a&gt;, viz. &lt;a href="https://wiki.lspace.org/Muntab"&gt;the Muntab question&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[&amp;hellip;] “For example, what about the Muntab question?”
Nanny Ogg asked the Muntab question. &amp;ldquo;Where the hell&amp;rsquo;s Muntab?&amp;rdquo; she said.
&amp;ldquo;Several thousand miles away, Mrs. Ogg. But it has ambitions Hubward, and if there&amp;rsquo;s war with Borogravia we will certainly have to adopt a position.&amp;rdquo;
&amp;ldquo;This one several thousand miles away looks fine by me,&amp;rdquo; said Nanny.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You could say that I am mostly stakeless and aware of it, so like Nanny,
I am not sure what to say. This is not so much cowardice as it is an
acute awareness of my own limitations. Please do not hold it against me.&lt;/p&gt;
&lt;p&gt;PS: I got several interesting anonymous comments about this article and
wanted to elaborate that this is not a &lt;em&gt;lack of empathy&lt;/em&gt; but rather a &lt;em&gt;lack
of understanding&lt;/em&gt; of large-scale geopolitical conflicts. I do not believe
that every conflict affords a simple good&amp;ndash;evil dichotomy, hence while
I empathise with the suffering of all those afflicted, I often do not
know enough about the ramifications of picking one side over the other.
Even in situations where this is possible, I believe my public opinion
on these conflicts to be largely irrelevant in the grand scheme of
things: I get the impression that I can only contribute to either
a very strong signal or increase the noise. The first action strikes me
as redundant, the second one as too chaotic.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;A position that I find untenable and, frankly, very dangerous.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;An affliction I seem to share with many academics.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Media can only help so much here, mostly because, depending on
which type of media one consumes, the definition of good and bad
can switch quite a bit.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Wed, 11 Dec 2024 17:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2024/stakeless/</guid></item><item><title>Generate Stunning Avatars Using OpenAI APIs</title><link>https://blog.adnansiddiqi.me/generate-stunning-avatars-using-openai-apis/</link><description>&lt;p&gt;This post is part of the GenAI Series. So far in the series, we have created AI apps that generate text. In this post, I will introduce OpenAI&amp;#8217;s image generation APIs, which can convert a textual prompt into an image and perform many other tasks related to images. I am going to make an app called AvatarAI. It will generate an avatar based on different parameters. Avatars are very common in the digital world. People use them mainly as profile pictures for forums, depicting their digital version. In case you are just interested in watching the demo, you can see it below: Prompt Engineering Like before, we will first create a prompt that generates avatars. This process is called prompt engineering. The prompt consists of two parts: the data in JSON format, which will be provided by users to define the look and feel of the avatar, and the instructions, which will guide OpenAI LLM on how to use that data. Below is the prompt: Generate an avatar based on the following specifications given in JSON format, ensuring all details are accurately reflected and styled as described. You must NOT write any text on the generated image {&amp;#8220;demographics&amp;#8221;: {&amp;#8220;gender&amp;#8221;: &amp;#8220;male&amp;#8221;, &amp;#8220;age&amp;#8221;: &amp;#8220;teen&amp;#8221;}, &amp;#8220;physicalCharacteristics&amp;#8221;: {&amp;#8220;skinTone&amp;#8221;: &amp;#8220;Tan&amp;#8221;, &amp;#8220;faceShape&amp;#8221;: &amp;#8220;Oval&amp;#8221;, &amp;#8220;eyes&amp;#8221;: {&amp;#8220;accessories&amp;#8221;: &amp;#8220;Glowing&amp;#8221;}, &amp;#8220;hairDetails&amp;#8221;: &amp;#8220;Wavy&amp;#8221;}, &amp;#8220;accessoriesAndClothing&amp;#8221;: {&amp;#8220;clothingType&amp;#8221;: &amp;#8220;Black Jacket&amp;#8221;, &amp;#8220;clothingColor&amp;#8221;: &amp;#8220;Metallic Tone&amp;#8221;}, &amp;#8220;environmentAndBackground&amp;#8221;: {&amp;#8220;theme&amp;#8221;: &amp;#8220;City Skyline&amp;#8221;, &amp;#8220;backgroundDetails&amp;#8221;: &amp;#8220;neon lights&amp;#8221;}, &amp;#8220;customization&amp;#8221;: {&amp;#8220;specialEffects&amp;#8221;: &amp;#8220;glow&amp;#8221;}, &amp;#8220;personalization&amp;#8221;: {&amp;#8220;interestsTheme&amp;#8221;: &amp;#8220;doctor&amp;#8221;}} Let&amp;#8217;s try it on chatGPT first: Not bad!  Now it&amp;#8217;s time to automate it. Development Assuming you have the OpenAI and Flask libraries installed, you will also need to install the library to fetch environment variables. If not, run the following commands: pip install openai pip install flask pip install python-dotenv The very first thing I am going to do is build the UI. Based on the above input, I asked GPT to come up with an interface like the one shown below: Looks cool, No? We are going to use OpenAI&amp;#8217;s Image APIs for this purpose. from openai import OpenAI if __name__ == '__main__': client = OpenAI( api_key="sk-trS-xxxxxx") prompt = "An ethereal and dramatic depiction inspired by Wordsworth's poem 'The World Is Too Much With Us.' A moonlit sea with waves reflecting silver light, the winds swirling like ethereal shapes. In the foreground, a serene meadow ('lea') with a figure gazing at the sea, Proteus rising from the waves, and Triton in the background blowing a majestic, spiraled horn." response = client.images.generate( model="dall-e-3", prompt="A stylized avatar of a tech-savvy individual with glowing neon glasses.", size="512x512", quality="standard", n=1, ) image_url = response.data[0].url print(response) Oops! It gave an error: openai.BadRequestError: Error code: 400 - {'error': {'code': 'invalid_size', 'message': 'The size is not supported by this model.', 'param': None, 'type': 'invalid_request_error'}} It seems I can&amp;#8217;t generate images of certain sizes. Upon reading I found this: hmm.., so for DALL-E 3 the minimum resolution I can produce is 1024&amp;#215;1024 Now, it produces the following output: ImagesResponse(created=1733810749, data=[Image(b64_json=None, revised_prompt='Create an illustration of a tech-savvy individual who has a futuristic look. The individual should be represented with glowing neon glasses that suggest their affinity for technology. The aura should be filled with lines of binary code cascading around them. The avatar should have an androgynous appearance to represent gender neutrality and can have any descent. The image should be highly stylized to encapsulate the modern, digital, and innovative essence of the tech world.', url='https://oaidalleapiprodscus.blob.core.windows.net/private/org-hiprYm3IyTNvRn2pAPp9cAPR/user-MCgDqt1tM3GQg7BictRVYOgv/img-OSV5bL9eikrP0uv5FonnDFGP.png?st=2024-12-10T05%3A05%3A49Z&amp;#38;se=2024-12-10T07%3A05%3A49Z&amp;#38;sp=r&amp;#38;sv=2024-08-04&amp;#38;sr=b&amp;#38;rscd=inline&amp;#38;rsct=image/png&amp;#38;skoid=d505667d-d6c1-4a0a-bac7-5c84a87759f8&amp;#38;sktid=a48cca56-e6da-484e-a814-9c849652bcb3&amp;#38;skt=2024-12-10T02%3A25%3A19Z&amp;#38;ske=2024-12-11T02%3A25%3A19Z&amp;#38;sks=b&amp;#38;skv=2024-08-04&amp;#38;sig=DwSbgi/omtXoHiXNdbymcO2Wj3mRt0PxmFgrPUXZ6uQ%3D')]) The ImageResponse object contains many things here. the parameter we are looking at here is url. It contains the URL of the generated image. You may also see the revised_prompt parameter that contains an internal prompt produced by GPT for internal usage. Now, I am not writing every bit of the code here, as I will share it anyway. Upon running the code, it generates something like the output shown below: Conclusion You see how easy it is to create an AI art generator app in Python using OpenAI APIs. The only thing that can hold you back is your creativity rather than the tools. You can customize this app as much as you want. By simply changing the prompt, you can transform this app into an AI logo generator app. The sky is the limit. As always, the code is available on GitHub. Looking to create something similar or even more exciting? Schedule a meeting or email me at kadnan @ gmail.com. Love What You’re Learning Here? If my posts have sparked ideas or saved you time, consider supporting my journey of learning and sharing. Even a small contribution helps me keep this blog alive and thriving. 👉 Support My Work Here &amp;#160; If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/generate-stunning-avatars-using-openai-apis/"&gt;Generate Stunning Avatars Using OpenAI APIs&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Wed, 11 Dec 2024 07:54:27 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/generate-stunning-avatars-using-openai-apis/</guid></item><item><title>How to build a fleet of networked offsite backups using Linux, WireGuard and rsync</title><link>https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;Just like most people out there, I have some files that are irreplaceable, such as cat pictures.&lt;/p&gt;
&lt;p&gt;At one point I had a few single-board computers sitting idle, namely
the &lt;a href="http://www.orangepi.org/html/hardWare/computerAndMicrocontrollers/details/Orange-Pi-Zero.html"&gt;Orange Pi Zero&lt;/a&gt; and
the &lt;a href="https://www.lattepanda.com/lattepanda-v1"&gt;LattePanda V1&lt;/a&gt;, and a few
1TB SSD-s.&lt;/p&gt;
&lt;p&gt;I hate idle hardware, so I did the most sensible thing and assembled a fleet of networked offsite backups for
backing up the most important data.&lt;/p&gt;
&lt;p&gt;My setup is based on various flavors of Linux, but the ideas will likely translate well onto other operating systems
and solutions.&lt;/p&gt;
&lt;h3 id="networking"&gt;Networking&lt;/h3&gt;
&lt;p&gt;The most important part is the networking. The offsite backup endpoints connect together to my home server over a
WireGuard network. The home server is, well, &lt;em&gt;the server&lt;/em&gt;, and backup endpoints are clients.&lt;/p&gt;
&lt;p&gt;I like &lt;a href="https://github.com/linuxserver/docker-WireGuard"&gt;this WireGuard Docker image&lt;/a&gt; a lot because it generates
the server and client configurations automatically, but you can use plain WireGuard or a completely different networking
solution to connect all the devices together. Some use Tailscale to make the setup process easier, but I like to keep
things as self-hosted as possible.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not a networking expert, but here&amp;rsquo;s how I&amp;rsquo;ve set up my network. For this example, the WireGuard network operates in
the &lt;code&gt;10.13.69.0/24&lt;/code&gt; range.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;To only allow traffic between the devices and avoid tunneling everything through the home server, set the &lt;code&gt;AllowedIPs&lt;/code&gt;
setting to &lt;code&gt;AllowedIPs = 10.13.69.0/24,10.13.69.1&lt;/code&gt;. We want to be able to access the backup endpoints, and nothing more.&lt;/p&gt;
&lt;p&gt;All the devices have a static IP address in that network, such as &lt;code&gt;10.13.69.1&lt;/code&gt; for the home server, &lt;code&gt;10.13.69.2&lt;/code&gt; for a
backup endpoint and so on.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;PersistentKeepalive = 25&lt;/code&gt; option is present in the client configurations so that I don&amp;rsquo;t lose the ability to access
the backup endpoints. With it, all the backup endpoints call back to the home server from time to time. The
aforementioned Docker image automatically adds it to the generated configuration using
the &lt;code&gt;PERSISTENTKEEPALIVE_PEERS=all&lt;/code&gt; option.
This setting is &lt;em&gt;&lt;strong&gt;crucial.&lt;/strong&gt;&lt;/em&gt; Without it, I sometimes ran into problems trying to connect from my home server to the
backup endpoint, and that&amp;rsquo;s something you can&amp;rsquo;t easily alleviate without having physical access to the backup endpoints,
which are offsite.&lt;/p&gt;
&lt;p&gt;Remove the DNS configuration from generated WireGuard client configurations, as you don&amp;rsquo;t need it for this purpose.&lt;/p&gt;
&lt;p&gt;Optionally, edit the &lt;code&gt;/etc/hosts&lt;/code&gt; file for the home server and backup endpoints so that you can access your backup
endpoints using simple hostnames, like &lt;code&gt;orangepizero&lt;/code&gt;. Example row can look like this: &lt;code&gt;10.13.69.6 orangepizero&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;if your WireGuard server operates in a network with a dynamic external IP address, as is common with many home internet
connections, I recommend getting yourself a domain name that you can update whenever your IP address changes and using
that in your WireGuard client configurations. Without this, an IP address change will result in your backup endpoints
being inaccessible.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll also likely need to set up port forwarding and/or traffic rules for your backup endpoints to be able to connect
back to your WireGuard server.&lt;/p&gt;
&lt;p&gt;Once you have the WireGuard connection set up and SSH running on the backup endpoints, you should be able to drop the
backup endpoints into any network that you have permission for. Ask your friends and family, and sweeten the deal by
offering free technical support or help in some other area in return. The cost of running a single-board computer 24/7
is minuscule with the typical power consumption being 1-3W, so that won&amp;rsquo;t be much of a concern.&lt;/p&gt;
&lt;h3 id="making-backups"&gt;Making backups&lt;/h3&gt;
&lt;p&gt;For making the actual backups themselves, you have all sorts of options.&lt;/p&gt;
&lt;p&gt;I rely on &lt;code&gt;rsync&lt;/code&gt; to copy the data over. It&amp;rsquo;s simple and it works, that&amp;rsquo;s all I expect from it.
Example command: &lt;code&gt;rsync -aAXvz /folder/to/back/up/ backupuser@backupendpoint:/backup/ --delete&lt;/code&gt;.
The files will be compressed during transit with the &lt;code&gt;-z&lt;/code&gt; option, and with &lt;code&gt;--delete&lt;/code&gt; you&amp;rsquo;ll ensure that the target
folder has all the files from the source, and nothing else.&lt;/p&gt;
&lt;p&gt;The backup storage is specified in &lt;code&gt;/etc/fstab&lt;/code&gt; with the &lt;code&gt;nofail&lt;/code&gt; option present. This ensures that in case the disk
dies, the backup endpoint will still boot properly, allowing me to access the machine to troubleshoot the issue and/or
force a desperate reboot to try to fix things. A good alternative approach is to mount/unmount the remote disk manually
as part
of the backup script.&lt;/p&gt;
&lt;p&gt;The backup storage uses the &lt;code&gt;btrfs&lt;/code&gt; filesystem, and I use &lt;code&gt;btrbk&lt;/code&gt; to take snapshots of the contents. If I accidentally
delete all the files on the backup endpoint, then I can still recover from that situation because the data is still
present in snapshots. 30 days is a good retention period: enough time to save the data in case of an accidental
deletion, but short
enough to avoid the backup disk getting full.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t want to use filesystem-level snapshots, then tools like &lt;code&gt;restic&lt;/code&gt; are a good alternative. It can also
operate
over SSH and you can configure snapshot retention policies in your backup script. Just make sure to not lose the
encryption password, and
verify the backups once in a while.&lt;/p&gt;
&lt;h3 id="deployment"&gt;Deployment&lt;/h3&gt;
&lt;p&gt;I manage my backup endpoints using some cobbled-together Ansible roles. I&amp;rsquo;ve perfected it to the point where the only
manual
steps are flashing the OS and setting up the storage, the rest is handled via Ansible.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;d like to share my work here, but it will make Jeff Geerling cry. Maybe one day I&amp;rsquo;ll take the time to improve
things&amp;hellip;&lt;/p&gt;
&lt;h3 id="maintenance"&gt;Maintenance&lt;/h3&gt;
&lt;p&gt;All the backup endpoints update and reboot themselves regularly. It&amp;rsquo;s just the sensible thing to do.&lt;/p&gt;
&lt;p&gt;Every 6-12 months I also do major OS version updates. It&amp;rsquo;s risky because of the whole offsite aspect of the solution,
but so far I haven&amp;rsquo;t been burned yet.&lt;/p&gt;
&lt;h3 id="monitoring"&gt;Monitoring&lt;/h3&gt;
&lt;p&gt;Monitoring is an area where I have some room for improvement. So far, I&amp;rsquo;ve set up Prometheus node-exporter to all of
the backup endpoints, and my home server keeps track of how the backup endpoints are doing.&lt;/p&gt;
&lt;p&gt;This allows me to check once in a while if any of the backup endpoints has fallen off the network, or if the backup disk
is getting full.&lt;/p&gt;
&lt;h3 id="issues-ive-faced"&gt;Issues I&amp;rsquo;ve faced&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve had this system running for a few years now, and it&amp;rsquo;s mostly stable! There have been some issues I&amp;rsquo;ve faced as
well,
though. Some are very specific to certain hardware, but I think there&amp;rsquo;s value in mentioning them.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ounapuu.ee/posts/2023/06/10/how-i-blew-up-my-backup-server/"&gt;I once blew up a backup server because of an Ansible configuration issue.&lt;/a&gt;
That meant that I had to physically go pick up the server to re-image it.&lt;/p&gt;
&lt;p&gt;The Orange Pi Zero was running quite hot, resulting in stability issues, so I put together a really janky cooling
solution.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/media/orangepizero.jpg"&gt;
        &lt;img alt="Cooling. It works." height="1000" src="https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/media/orangepizero_hu_c4b5d89d125d09fe.jpg" style="width: auto; height: auto; border-radius: 8px;" width="750" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Cooling. It works.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Hell, I did the same for the LattePanda as well.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/media/lattepanduh.jpg"&gt;
        &lt;img alt="First version of the cooling upgrade on the LattePanda." height="750" src="https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/media/lattepanduh_hu_836282b7f71b7fef.jpg" style="width: auto; height: auto; border-radius: 8px;" width="1000" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      First version of the cooling upgrade on the LattePanda.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;It might look horrific but the extra cooling has fixed all the stability problems on both boards.&lt;/p&gt;
&lt;p&gt;The lack of a real-time clock on the LattePanda has required me to make its backup script a bit special. I can&amp;rsquo;t rely on
a systemd timer that automatically reboots the machine once in a while, so instead that part is present in the backup
script. The issue is that the LattePanda boots up with the time being set in the past, and once it gets the actual time
from the
network, it will run all sorts of tasks because enough time has passed!
This included the reboot timer as well.&lt;/p&gt;
&lt;p&gt;At one point, the power supply on the LattePanda just died, and it was very visible on my graphs. That required a
replacement.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/media/lattepanda-psu-failure.png"&gt;
        &lt;img alt="Signs that you might have a failing power supply." height="725" src="https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/media/lattepanda-psu-failure_hu_b5a1b6c6246e83e2.png" style="width: auto; height: auto; border-radius: 8px;" width="1000" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Signs that you might have a failing power supply.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;That&amp;rsquo;s how I back up the most important data. I hope that this has given you inspiration to take your own backup
approach to the next level!&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;yes, I do plan to move this setup to IPv6 eventually.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>./techtipsy</author><pubDate>Wed, 11 Dec 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/12/11/wireguard-backup-fleet/</guid></item><item><title>Sex development, puberty, and transgender identity</title><link>https://denovo.substack.com/p/sex-development-puberty-and-transgender</link><description>Reproductive Biology for for Politicians and Voters, Part 2A</description><author>De Novo</author><pubDate>Tue, 10 Dec 2024 23:30:57 GMT</pubDate><guid isPermaLink="true">https://denovo.substack.com/p/sex-development-puberty-and-transgender</guid></item><item><title>[AI Act Conference 2024 - Notes] Legislating Technology: Addressing the law's internal dilemma</title><link>https://liza.io/ai-act-conference-2024-notes-legislating-technology-addressing-the-laws-internal-dilemma/</link><description>&lt;p&gt;These are my notes from one of the AI Act Conference presentations from December 10, 2024.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Addressing the law&amp;rsquo;s internal dilemma&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Stanley Greenstein, Associate Professor in Law and IT, Faculty of Law, Stockholm University.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This work examines the general regulation of technology and is a work in progress.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Tue, 10 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/ai-act-conference-2024-notes-legislating-technology-addressing-the-laws-internal-dilemma/</guid></item><item><title>Generative AI is digital homeopathy — how I train my own model</title><link>https://markgreville.ie/2024/12/10/generative-ai-is-digital-homeopathy-how-i-train-my-own-model/</link><description>If it&amp;#8217;s your first time here, you may be surprised at how few pieces I have written. (After reading for a while, you may even be glad of this fact). When friends bring up what they assume is a painful subject, they get a faraway look in their eyes. They place a gentle hand on [&amp;#8230;]</description><author>Mark Greville</author><pubDate>Tue, 10 Dec 2024 18:49:02 GMT</pubDate><guid isPermaLink="true">https://markgreville.ie/2024/12/10/generative-ai-is-digital-homeopathy-how-i-train-my-own-model/</guid></item><item><title>Using diskimage-builder with a single yaml file</title><link>https://jay.jvf.cc/posts/diskimage-builder-yaml/</link><description>&lt;h1 id="diskimage-builder-with-a-single-yaml-file"&gt;diskimage-builder with a single yaml file&lt;/h1&gt;
&lt;h2 id="why-diskimage-builder"&gt;Why diskimage-builder&lt;/h2&gt;
&lt;p&gt;With the recent move of OpenStack devstack to require/support Ubuntu 24.04
Noble, I needed to make some changes to my local environment. Previously,
I was using an unmodified Ubuntu cloud image as a base, supplying a cloud-init
ISO (created by cockpit-machines) to do just-in-time user and network
configuration.&lt;/p&gt;
&lt;p&gt;For environmental reasons mostly irrelevant to this post, that approach doesn&amp;rsquo;t
work for me anymore. So I decided to use &lt;a href="https://docs.openstack.org/diskimage-builder/latest/"&gt;diskimage-builder&lt;/a&gt;
to create an image that&amp;rsquo;s ready to go from get-go. While I&amp;rsquo;ve used
diskimage-builder in the past &amp;ndash; I actually just fixed the Gentoo element for
it &amp;ndash; I have typically used it to create ramdisks for IPA, and rarely to create
my own cloud base images.&lt;/p&gt;</description><author/><pubDate>Tue, 10 Dec 2024 18:20:00 GMT</pubDate><guid isPermaLink="true">https://jay.jvf.cc/posts/diskimage-builder-yaml/</guid></item><item><title>untitled excerpt</title><link>https://asemic-horizon.com/2024/12/10/untitled-excerpt/</link><description>&amp;#8230;. ECONOMIC GROWTH: if there was a single phenomenon that could be characterized as &amp;#8220;emergent&amp;#8221;, this would be it. Dave Snowden posted something about purpose (or was it strategy? in many ways it doesn&amp;#8217;t really matter) being an emergent process rather than a north star. The point being that you can&amp;#8217;t beam whatever makes orgs [&amp;#8230;]</description><author>asemic horizon</author><pubDate>Tue, 10 Dec 2024 13:40:44 GMT</pubDate><guid isPermaLink="true">https://asemic-horizon.com/2024/12/10/untitled-excerpt/</guid></item><item><title>Amazon Scraper: Get ANY Product Data from Amazon without coding</title><link>https://blog.adnansiddiqi.me/amazon-scraper-get-any-product-data-from-amazon-without-coding/</link><description>&lt;p&gt;As the world&amp;#8217;s leading e-commerce platform, Amazon not only provides consumers with a seamless and reliable one-stop shopping experience but also serves as a critical data aggregation hub. Its vast repository of product information enables sellers to perform in-depth market research and develop effective sales strategies. How to integrate a substantial amount of data in a structured way to give your e-commerce business a real leg up? It is not a smart move to manually search, paste, and copy the required information directly; while using code for web scraping poses a technical barrier for people lacking a programming background. Fortunately, there is a user-friendly intelligent web scraper available that can swiftly extract data like product prices, ratings, reviews, and virtually all pertinent information within just a few seconds. In this article, we&amp;#8217;d like to walk you through the basic process of scraping product data from Amazon without code. Amazon Web Scraping You Should Know Is it legal to scrape Amazon Web scraping and crawling are not inherently illegal; however, they can become problematic when employed for malicious activities. These include intellectual property infringement, data theft, account hijacking, or any other unlawful intentions. As a web scraping tool is merely a means to an end, you&amp;#8217;re suggested to adhere to the principle of not collecting any private information. Additionally, always review the terms of use on the target websites before starting the data scraping process to avoid any unforeseen complications. What data can be extracted from Amazon Generally speaking, you have access to a comprehensive range of data that is visually presented on the Amazon web pages. To illustrate further, let me enumerate some of the primary types of data that you can successfully procure from this online platform. Listing page: title, price, stars, number of reviews, estimated delivery time, etc. Detail page: product description, customer reviews, inventories, images, questions, etc. Why collect product data from Amazon Scraping statistics from Amazon can provide a multitude of benefits for businesses. Here are a variety of scenarios where web scraping Amazon can be helpful: Market Research Competitive Analysis: By collecting data on similar products, sellers can analyze their competitors&amp;#8217; pricing, sales, and reviews, gain insights into the market, and adjust their pricing(price monitoring and dynamic price) and marketing strategies accordingly. Trend Identification: Web scraping facilitates the recognition of emerging trends and sought-after products in the marketplace, which can inform future product development and inventory management. Customer Insights Review Analysis: Amazon reviews provide a wealth of statistics concerning customer preferences, pain points, and feedback. Analyzing this data can help businesses optimize their products and enhance customer service. Sentiment Analysis: By analyzing customer sentiment, businesses can gauge how their products are being received in the market and make necessary adjustments. How to Configure a No-Code Amazon Scraper With the assistance of the user-friendly web scraping tool, extracting data has become a breeze even for those without coding experience. Octoparse is an easy-to-use web scraper that is accessible to everyone. To get started, simply download and install the software on your device. Next, create a free account to log in and familiarize yourself with the fundamental settings. Please be assured that Octoparse offers step-by-step live demo tutorials specifically designed for beginners! You can also kickstart your learning process by watching videos of hands-on Amazon data-scraping experiences shared by bloggers. Following that, you can embark on your journey of crawling data with the procedures listed below. Step 1: Create a new task with the URL Once you have entered the keyword or ASIN and navigated to the webpage containing the data you aim to extract, simply copy and paste the target URL into Octoparse&amp;#8217;s search bar. Click the &amp;#8220;Start&amp;#8221; button to initiate a new task, and within a matter of seconds, the page will load in Octoparse&amp;#8217;s built-in browser. Step 2: Auto-detect data on the Amazon webpage Once the page has finished loading, click &amp;#8220;Auto-detect webpage data&amp;#8221; in the Tips panel to let Octoparse scan the page and detect data fields for you. It&amp;#8217;ll highlight all detected data for you to locate and preview extractable data. If there are any unwanted data fields, you can also remove them from the Data Preview section at the bottom. Step 3: Create and modify a workflow After ensuring the accuracy and completeness of the data, you can click &amp;#8220;Create workflow&amp;#8221; and then a workflow will show up on the right-hand side. This diagram is actually a flowchart illustrating how this scraper works. You can read the workflow from top to bottom, from inside to outside if there are any nested actions. To check if the scraper works as intended, click on each step on the diagram. This will allow you to preview and see how it functions at each stage. The aforementioned content primarily shows the process of collecting listing page data using Octoparse. If you also want to gather detailed page data concurrently, you can refer to their tutorial video. Step 4: Run the task and export Amazon product data After you have reviewed the entire workflow, launch the scraper by clicking &amp;#8220;Run.&amp;#8221; You can choose to run the task on your local device or on Octoparse&amp;#8217;s cloud servers. Each option has its own advantages: running locally is suitable for smaller-scale projects, while cloud servers are more efficient for handling large-scale tasks. Select the option that best fits your needs, and Octoparse will take care of the rest. Once the scraping process is complete, you can export the data in various formats, such as Excel, CSV, or JSON files, or even to a database like Google Sheets and Amazon Web Services, for further analysis and use. Preset templates for Amazon data scraping To better streamline your data collection and analysis, Octoparse has curated an extensive range of 20 refined Amazon scraper templates for selection. These templates cater to a variety of scraping requirements, including gathering information from listing pages or detail pages, as well as extracting product details or user reviews via ASIN or keywords. Furthermore, they offer tailored scraping tool versions suited for different countries. Warp-up To sum up, by aggregating data from Amazon, sellers can gain effective means to compare product prices, assess consumer reviews, and obtain other insightful information. With Octoparse, the technical barrier of requiring coding knowledge for web scraping has been eliminated, making it possible for everyone to easily access and utilize the integrated data. Therefore, no-code web scraper can definitely be a right-hand tool for anyone doing Amazon business data analysis. Octoparse goes beyond just that, offering a wide range of scraping opportunities including lead generation, social media data, higher education research, and content and news aggregation&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/amazon-scraper-get-any-product-data-from-amazon-without-coding/"&gt;Amazon Scraper: Get ANY Product Data from Amazon without coding&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Tue, 10 Dec 2024 10:57:41 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/amazon-scraper-get-any-product-data-from-amazon-without-coding/</guid></item><item><title>Coding on autopilot</title><link>https://iam.mt/coding-on-autopilot/</link><description>Over-relying on copilots for programming puts you on autopilot. It creates mental laziness and that is something to be avoided.</description><author>Mohnish Thallavajhula</author><pubDate>Tue, 10 Dec 2024 09:46:40 GMT</pubDate><guid isPermaLink="true">https://iam.mt/coding-on-autopilot/</guid></item><item><title>How to automatically switch git profiles based on the current directory</title><link>https://zackproser.com/blog/multiple-git-profiles-automated</link><description>You can automate your git profile switching based on the current directory. Here's how to do it.</description><author>Zachary Proser</author><pubDate>Tue, 10 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/multiple-git-profiles-automated</guid></item><item><title>AI Act Conference 2024 Keynote notes: Navigating the AI Act</title><link>https://liza.io/ai-act-conference-2024-keynote-notes-navigating-the-ai-act/</link><description>&lt;p&gt;Today I listened in to part of the AI Act Conference hosted by Uppsala University. Below are my notes from the keynote.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Mon, 09 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/ai-act-conference-2024-keynote-notes-navigating-the-ai-act/</guid></item><item><title>On C# and .NET quick release cycle</title><link>https://nicolaiarocci.com/on-csharp-and-dotnet-quick-release-cycle/</link><description>&lt;p&gt;&lt;em&gt;I sat to jot down a quick introduction to my &lt;a href="https://nicolaiarocci.com/speaking-at-the-dotnet-conference-italia-2024/"&gt;C# 13 What&amp;rsquo;s New and Interesting&lt;/a&gt; session next week, and what I ended up with instead is a long rant or, should I dare, stream of consciousness that is certainly inappropriate for a five-minute introduction. I&amp;rsquo;ll have to cut most of it down, especially on the personal story part, but my site might be a good place to host it in all its completeness. I may reference this post at the start of the session on Monday for those few poor souls who might be interested.&lt;/em&gt;&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Mon, 09 Dec 2024 18:31:18 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/on-csharp-and-dotnet-quick-release-cycle/</guid></item><item><title>I built a community of 1,600... accidentally</title><link>https://martinrue.com/accidental-community</link><description>When I say community, I mean social network. Yes, I made my own social network. It doesn't get much more narcissistic than that.</description><author>Martin Rue</author><pubDate>Mon, 09 Dec 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/accidental-community</guid></item><item><title>An experimental (e)shell pager</title><link>https://xenodium.com/an-experimental-e-shell-pager</link><description>&lt;p&gt;These days, my LLM interactions primarily take place via &lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt;'s compose UX. I've grown fond of this hybrid style of interaction. On sharing the &lt;a href="https://bsky.app/profile/xenodium.bsky.social/post/3lco3ggkk4c2r"&gt;latest incarnation&lt;/a&gt;, &lt;a href="https://bsky.app/profile/jllw.bsky.social"&gt;jllw&lt;/a&gt; asked about the possibility to &lt;a href="https://bsky.app/profile/jllw.bsky.social/post/3lcpb6ygkws2v"&gt;port to comint shells&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With &lt;a href="https://bsky.app/profile/jllw.bsky.social"&gt;jllw&lt;/a&gt;'s great idea in mind, I set out to prototype an &lt;a href="https://www.gnu.org/software/emacs/manual/html_mono/eshell.html"&gt;eshell&lt;/a&gt; pager, my preferred shell.&lt;/p&gt;
&lt;p&gt;The initial results ain't too shabby:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/an-experimental-e-shell-pager/shell-pager.gif" /&gt;&lt;/p&gt;
&lt;p&gt;While you can easily flip through all &lt;code&gt;eshell&lt;/code&gt; commands and outputs (via single-character n/p bindings), you can also compose and fire off new commands using a magit-style approach. That is, craft your desired command and C-c C-c to send it off.&lt;/p&gt;
&lt;p&gt;The prototype is currently focused on &lt;code&gt;eshell&lt;/code&gt;, but it can be easily ported to &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Shell-Mode.html"&gt;comint&lt;/a&gt; and other shells by writing new &lt;code&gt;shell-maker&lt;/code&gt; configs:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(shell-pager--make-config
 :shell-buffer buffer
 :page-buffer (shell-pager--buffer)
 :next #'shell-pager--eshell-next
 :previous #'shell-pager--eshell-previous
 :current #'shell-pager--eshell-current-item
 :subscribe #'shell-pager--eshell-subscribe
 :unsubscribe #'shell-pager--eshell-unsubscribe
 :submit #'shell-pager--eshell-submit
 :interrupt #'shell-pager--eshell-interrupt)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;shell-pager&lt;/code&gt;'s super &lt;a href="https://github.com/xenodium/shell-pager"&gt;rough/experimental code is now on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It's been a fun experiment so far. Time will tell, whether or not &lt;code&gt;shell-pager&lt;/code&gt; can replace most of my &lt;code&gt;eshell&lt;/code&gt; interactions. The good news is that this isn't an all or nothing sorta thing. &lt;code&gt;shell-pager&lt;/code&gt; complements &lt;code&gt;eshell&lt;/code&gt;, so I can always jump back to eshell itself and continue with the very same shell history.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Mon, 09 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/an-experimental-e-shell-pager</guid></item><item><title>App Config Design</title><link>https://www.craigpardey.com/post/2024-12-09-app-config-design/</link><description>&lt;p&gt;I wrote about &lt;a href="https://www.craigpardey.com/post/2015-03-30-dry-config"&gt;DRY configs&lt;/a&gt; many years ago but astonishingly my clients don&amp;rsquo;t seem to read my blog. I mean, who even &lt;em&gt;has&lt;/em&gt; a blog these days anyway?&lt;/p&gt;
&lt;p&gt;Externalized application configs have a way of multiplying like rabbits to the point where people become afraid to touch them at all, let alone apply the &lt;a href="https://en.wikipedia.org/wiki/Don't_repeat_yourself"&gt;DRY principle&lt;/a&gt; to them. The proliferation of poorly-designed configuration files in Enterprise Java systems is appalling, and the situation is compounded when an external configuration system like Consul or Spring Cloud Config is added to the mix.&lt;/p&gt;</description><author>Craig Pardey</author><pubDate>Mon, 09 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.craigpardey.com/post/2024-12-09-app-config-design/</guid></item><item><title>Parsing MIDI messages in Rust</title><link>https://ntietz.com/blog/parsing-midi-rust/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;I'm working on a terrible idea of a project, and this project uses MIDI.
That means I need a MIDI implementation!
I chose to use an existing library, &lt;a href="https://github.com/Boddlnagg/midir"&gt;midir&lt;/a&gt;, to connect to devices and receive messages.
But the reason I was interested in this not-yet-announced project is because I wanted to understand MIDI.
So it was time to implement the communication protocol myself.&lt;/p&gt;
&lt;h1 id="what-is-midi-and-why-s-it-cool"&gt;What is MIDI and why's it cool?&lt;/h1&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/MIDI"&gt;MIDI&lt;/a&gt; stands for Musical Instrument Digital Interface, and it really doesn't bury the lede.
It's a standard for digital musical instruments communicate!
The standard includes both the electronics and hardware, and it includes the communication protocol.
This post is only concerned with the communication protocol.&lt;/p&gt;
&lt;p&gt;In 1980, there was no standard digital interface for how instruments communicate&lt;sup class="footnote-reference" id="fr-cv-1"&gt;&lt;a href="https://ntietz.com/blog/parsing-midi-rust/#fn-cv"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
You had electronic instruments, and it would be nice to put them together, but manufacturers did their own things.
Roland created a couple of protocols for their own devices, then pulled in some folks from other companies to make a standard.
This eventually became MIDI, and the first MIDI device was released in 1983.&lt;/p&gt;
&lt;p&gt;Since then, some new functionality has been added, but the core has remained the same.
There are a few new(er) standards that are in various stages of use.
For example, &lt;a href="https://en.wikipedia.org/wiki/Open_Sound_Control"&gt;Open Sound Control (OSC)&lt;/a&gt; is used in some instruments and applications, and was created in the early 2000s.
And &lt;a href="https://en.wikipedia.org/wiki/MIDI#MIDI_2.0"&gt;MIDI 2.0&lt;/a&gt; has been announced but doesn't have widespread adoption.&lt;/p&gt;
&lt;p&gt;MIDI, the original, is still in widespread use because it works and it's ubiquitous.
Let's marvel at that a little bit: this protocol has lasted over &lt;em&gt;40 years&lt;/em&gt;, and it has a successor which isn't widely implemented.
It certainly has some flaws and quirks, and it has limitations, and that's why we'll eventually see it replaced or surpassed.
But its longevity is simply incredible.&lt;/p&gt;
&lt;p&gt;When you connect devices with MIDI, each can send and receive messages.
These messages let you do things like turn on and off a specific note (play A4 at this volume), bend the pitch, and change parameters of the synthesizer.
It also lets you do things like synchronize timing, select a song to play, or perform manufacturer-specific commands.&lt;/p&gt;
&lt;p&gt;A common MIDI use case is connecting a controller (a keyboard, wind controller, or other instrument with controls you can activate) to a synthesizer (hardware or software).
Not every electronic instrument can make sound on its own, and this lets you decouple those pieces!
You can also connect multiple devices together, so you can have one sequencer that's recording MIDI events and replaying them, and then have other controllers feed into that, and have the whole system output to a synthesizer (or use one that's onboard).
There's a lot of flexibility and you can do some really neat things with MIDI, especially since you can edit the actual note on/off events and their timing and play around with those!&lt;/p&gt;
&lt;p&gt;So... all &lt;em&gt;I&lt;/em&gt; want with MIDI is to make my computer listen to some MIDI messages and do something based on them.
Let's look at how the protocol works.&lt;/p&gt;
&lt;h1 id="the-basic-workings"&gt;The basic workings&lt;/h1&gt;
&lt;p&gt;The fundamental unit of the MIDI protocol is a message.
Each message has one status byte, followed by some number of data bytes.
A status byte starts with a leading 1, and data bytes have a leading 0 (so they are effectively 7-bit bytes)&lt;sup class="footnote-reference" id="fr-bits-1"&gt;&lt;a href="https://ntietz.com/blog/parsing-midi-rust/#fn-bits"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;And each message falls into a particular group.
The main groups of messages are voice messages, system common messages, and system real-time messages&lt;sup class="footnote-reference" id="fr-but-also-1"&gt;&lt;a href="https://ntietz.com/blog/parsing-midi-rust/#fn-but-also"&gt;[3]&lt;/a&gt;&lt;/sup&gt;.
Voice messages tell you about playing sounds: note on/off, key pressure, pitch bends, etc.
System common messages let you do manufacturer-specific things and control positioning in songs/sequences.
And system real-time messages are for timing, mostly.&lt;/p&gt;
&lt;p&gt;The overall structure of a MIDI message is a status byte followed by some data bytes.
For example, if we have two data bytes, we could draw it like this.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Illustration of MIDI protocol as one status byte and multiple data bytes" src="https://ntietz.com/images/midi-1.png" title="Illustration of MIDI protocol as one status byte and multiple data bytes" /&gt;&lt;/p&gt;
&lt;p&gt;What can the status be, and what do the data bytes represent?
It depends on the kind of message.&lt;/p&gt;
&lt;h2 id="voice-messages"&gt;Voice messages&lt;/h2&gt;
&lt;p&gt;There are seven voice messages: Note Off, Note On, Aftertouch, Control Change, Program Change, Channel Pressure, and Pitch Wheel.&lt;/p&gt;
&lt;p&gt;We might get a message with the value 0x904851 (three bytes, in hex&lt;sup class="footnote-reference" id="fr-hex-1"&gt;&lt;a href="https://ntietz.com/blog/parsing-midi-rust/#fn-hex"&gt;[4]&lt;/a&gt;&lt;/sup&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Illustration of a message with three bytes: 0x90, 0x48, and 0x51." src="https://ntietz.com/images/midi-2.png" title="Illustration of a message with three bytes: 0x90, 0x48, and 0x51." /&gt;&lt;/p&gt;
&lt;p&gt;To parse this, we deal with the status byte first.
For voice messages, we split this into two pieces: the first nibble (four bits) is the category of message, and the second nibble is the channel (values 0-15, representing channels 1-16).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Illustration of the previous message showing the nibbles which correspond to the category and channel." src="https://ntietz.com/images/midi-3.png" title="Illustration of the previous message showing the nibbles which correspond to the category and channel." /&gt;&lt;/p&gt;
&lt;p&gt;So when we look at this nibbles for our message, we see that this message is the category 0x9 and the channel is 0x0.
0x9 denotes a Note On event, which for a keyboard is sent when a key is pressed down.&lt;/p&gt;
&lt;p&gt;Given it's Note On, we expect two data bytes to follow.
The first, 0x48, is the note number.
The second, 0x51, is the velocity.
So our note is 72 with a velocity of 81!
This corresponds to a C4 at roughly 60% of the available volume.&lt;/p&gt;
&lt;p&gt;The rest of the voice messages are parsed in the same way, with 1 or 2 data bytes.
Program Change and Channel Pressure have one data byte, and the rest have two data bytes.
The data bytes have different meanings based on the category, but most of these are either a single 7-bit value or a pair of 7-bit values.
The exception is Pitch Wheel, which is a 14-bit value that you reconstruct from the two 7-bit halves.&lt;/p&gt;
&lt;p&gt;A fun part of the spec here: If you send a Note On message with velocity of 0, it must be interpreted as a Note Off message (which itself &lt;em&gt;also&lt;/em&gt; has a velocity for the release speed).
The keyboard I have functions like this, only sending Note On, while my wind synth sends both types.
This is all valid and compliant with the spec.&lt;/p&gt;
&lt;h2 id="system-common-messages"&gt;System common messages&lt;/h2&gt;
&lt;p&gt;These are much like voice messages, but the status byte is used entirely for what kind of message it is.
These are general to &lt;em&gt;all&lt;/em&gt; connected MIDI devices, so we don't specify the channel.&lt;/p&gt;
&lt;p&gt;There are five system common messages: System Exclusive, MIDI Time Code, Song Position Pointer, Song Select, and Tune Request.&lt;/p&gt;
&lt;p&gt;Tune Request has &lt;em&gt;no&lt;/em&gt; data bytes, so it's just the one status byte.
Song Position Pointers are parsed like Pitch Wheel, where we have two data bytes which form one 14-bit value.
Song Select is parsed as a single byte value which specifies the song number.
And MIDI Time Code is also a single byte, but it's parsed as two nibbles for a message type and value.&lt;/p&gt;
&lt;p&gt;The most interesting one is System Exclusive (SysEx).
This basically gives us arbitrary per-manufacturer message types, and anyone else is supposed to close their ears and ignore that one if it's not for them.
Some of these are things like bulk data dumps or listing patch parameters.&lt;/p&gt;
&lt;p&gt;SysEx messages start with 1 or 3 bytes for the manufacturer ID, and then the rest is data.
These messages are arbitrary length, and are terminated by finding the byte 0xF7.&lt;/p&gt;
&lt;h2 id="system-real-time-messages"&gt;System real-time messages&lt;/h2&gt;
&lt;p&gt;The final category of messages are system real-time messages.
To me, these seem both simple and &lt;em&gt;utterly cursed&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The simple part: there are seven messages, each of which is only one byte.
You have Clock, Tick, Start, Stop, Continue, Active Sensing, and Reset.&lt;/p&gt;
&lt;p&gt;Let's use Clock as an example.
It's sent 24 times per quarter note, and the entire message is its status byte: 0xF8.&lt;/p&gt;
&lt;p&gt;Okay, what's wrong with that?&lt;/p&gt;
&lt;p&gt;Nothing, the message itself is fine.
It's just where you can put it.&lt;/p&gt;
&lt;p&gt;What seems utterly cursed is that you can put these &lt;em&gt;anywhere&lt;/em&gt; and it's valid.
In between bytes of other messages, sure!
So the Note On message we had could be received instead as 0x9048F851.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Illustration of the previous message showing a real-time message received in the middle of a note on message." src="https://ntietz.com/images/midi-4.png" title="Illustration of the previous message showing a real-time message received in the middle of a note on message." /&gt;&lt;/p&gt;
&lt;p&gt;Like... I get it.
This means we can send these messages at exact times so that timing is locked in.
But the rest of the messages, except SysEx, are at most 3 bytes total.
It seems a little unnecessary to do this!
And it makes parsing more complicated, because you have to check each byte for if it's a system real-time message, instead of knowing that the next couple of bytes are definitely for this message.&lt;/p&gt;
&lt;h1 id="a-parser-combinator-appears"&gt;A parser combinator appears&lt;/h1&gt;
&lt;p&gt;Now that we've looked at how the protocol works, let's parse it!
There are a variety of ways to write parsers in Rust.
I chose to use a parser combinator since it's a relatively simple approach here, and it lets us write a lot of reusable code.&lt;/p&gt;
&lt;h2 id="structuring-our-data"&gt;Structuring our data&lt;/h2&gt;
&lt;p&gt;First we need the structures we're parsing into.
We can define enums for each of the three message groups.
There's an extra in each of these—&lt;code&gt;Unknown&lt;/code&gt;—to provide a fallback if we run into one of the reserved status bytes.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;derive&lt;/span&gt;&lt;span&gt;(PartialEq, Eq, Debug, Clone)]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub enum &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;VoiceCategory &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    NoteOff { note: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;, velocity: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8 &lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;span&gt;    NoteOn { note: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;, velocity: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8 &lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;span&gt;    AfterTouch { note: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;, pressure: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8 &lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;span&gt;    ControlChange { controller: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;, value: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8 &lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;span&gt;    ProgramChange { value: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8 &lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;span&gt;    ChannelPressure { pressure: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8 &lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;span&gt;    PitchWheel { value: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u16 &lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;span&gt;    Unknown,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;derive&lt;/span&gt;&lt;span&gt;(PartialEq, Eq, Debug, Clone)]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub enum &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;SystemCommon &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    SystemExclusive { data: &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;&amp;gt; },
&lt;/span&gt;&lt;span&gt;    MidiTimeCode { time_code: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8 &lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;span&gt;    SongPositionPointer { value: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u16 &lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;span&gt;    SongSelect { song_number: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8 &lt;/span&gt;&lt;span&gt;},
&lt;/span&gt;&lt;span&gt;    TuneRequest,
&lt;/span&gt;&lt;span&gt;    Unknown,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;derive&lt;/span&gt;&lt;span&gt;(PartialEq, Eq, Debug, Clone)]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub enum &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;SystemRealtime &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    Clock,
&lt;/span&gt;&lt;span&gt;    Tick,
&lt;/span&gt;&lt;span&gt;    Start,
&lt;/span&gt;&lt;span&gt;    Stop,
&lt;/span&gt;&lt;span&gt;    Continue,
&lt;/span&gt;&lt;span&gt;    ActiveSense,
&lt;/span&gt;&lt;span&gt;    Reset,
&lt;/span&gt;&lt;span&gt;    Unknown,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we define a struct for the VoiceMessage, since we also want the channel information.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;derive&lt;/span&gt;&lt;span&gt;(PartialEq, Eq, Debug, Clone)]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub struct &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;VoiceMessage &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;category&lt;/span&gt;&lt;span&gt;: VoiceCategory,
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;channel&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;impl &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;VoiceMessage &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;new&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;category&lt;/span&gt;&lt;span&gt;: VoiceCategory, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;channel&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;) -&amp;gt; VoiceMessage {
&lt;/span&gt;&lt;span&gt;        VoiceMessage { category, channel }
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we make a high-level enum to contain each of the message groups.
This approach lets us treat entire groups of messages in the same way, rather than having to match on each individual message type.
You could certainly make one big enum for all of them, though!&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;derive&lt;/span&gt;&lt;span&gt;(PartialEq, Eq, Debug, Clone)]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub enum &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;Message &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    Voice(VoiceMessage),
&lt;/span&gt;&lt;span&gt;    System(SystemCommon),
&lt;/span&gt;&lt;span&gt;    Realtime(SystemRealtime),
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="building-our-parser"&gt;Building our parser&lt;/h2&gt;
&lt;p&gt;Parser combinators are neat because they let you combine small, discrete pieces into a larger whole.
You define small parsers, then build your parser by combining these together in various ways!&lt;/p&gt;
&lt;h3 id="top-level-parser"&gt;Top-level parser&lt;/h3&gt;
&lt;p&gt;We'll start at the high level.
Since we have three different message types, we'll define three parsers, one for each message type.
So our top-level parser will return the main &lt;code&gt;Message&lt;/code&gt; enum from above, and it will call each of our individual parsers in turn.
Which parser to call is determined by the range the status byte is in.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;parse_message&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]) -&amp;gt; IResult&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], Message&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(bytes, status_byte) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;take&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;1&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;usize&lt;/span&gt;&lt;span&gt;)(bytes)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; status_byte &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span&gt; status_byte[&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;];
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-style: italic; color: #928374;"&gt;// TODO: implement running status; see [1].
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-style: italic; color: #928374;"&gt;// [1]: http://midi.teragonaudio.com/tech/midispec/run.htm
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if&lt;/span&gt;&lt;span&gt; status_byte &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xF0 &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(bytes, vm) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;parse_voice_message&lt;/span&gt;&lt;span&gt;(status_byte, bytes)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((bytes, Message::Voice(vm)))
&lt;/span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;else if&lt;/span&gt;&lt;span&gt; status_byte &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xf8 &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(bytes, sc) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;parse_system_common&lt;/span&gt;&lt;span&gt;(status_byte, bytes)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((bytes, Message::System(sc)))
&lt;/span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;else &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; sr &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;parse_system_realtime&lt;/span&gt;&lt;span&gt;(status_byte);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((bytes, Message::Realtime(sr)))
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A few things to note in &lt;code&gt;parse_message&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;take&lt;/code&gt; is a parser that's defined by nom for us, and calling it defines a new parser that takes the specified number of bytes. When you invoke this parser on &lt;code&gt;bytes&lt;/code&gt;, the return value is a tuple of the remaining bytes after parsing, along with a slice of the taken bytes.&lt;/li&gt;
&lt;li&gt;There's a TODO in here, because you can implement running status, which lets the status byte be omitted if nothing else has happened in the meantime. I chose to ignore this for now, until some piece of hardware requires I implement it.&lt;/li&gt;
&lt;li&gt;The library I'm using for MIDI device discovery &lt;em&gt;also&lt;/em&gt; chunks messages for me, so I don't think I can run into the interleaved messages situation in this code, which is why I have a separate parser for it and am ignoring that here.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now let's look at how those child parsers are implemented!&lt;/p&gt;
&lt;h3 id="parsing-system-real-time-messages"&gt;Parsing system real-time messages&lt;/h3&gt;
&lt;p&gt;System real-time messages are the simplest, since they're just one byte, so we can knock that parser out quickly.
It's just a big old match statement.
We check the byte that's passed in and we return the appropriate value.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;parse_system_realtime&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;status_byte&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;) -&amp;gt; SystemRealtime {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;match&lt;/span&gt;&lt;span&gt; status_byte {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xf8 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;SystemRealtime::Clock,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xf9 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;SystemRealtime::Tick,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xfa &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;SystemRealtime::Start,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xfb &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;SystemRealtime::Continue,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xfc &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;SystemRealtime::Stop,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xfe &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;SystemRealtime::ActiveSense,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xff &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;SystemRealtime::Reset,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;_ =&amp;gt; &lt;/span&gt;&lt;span&gt;SystemRealtime::Unknown,
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="interlude-helper-parsers"&gt;Interlude: helper parsers&lt;/h3&gt;
&lt;p&gt;Okay, so I know what's coming up for the other two parsers we need.
We'll need some helper functions or we'll have a lot of repetition.
Let's knock those out here for clarity of exposition.&lt;/p&gt;
&lt;p&gt;We'll need to handle forming messages from one data byte or two data bytes, and we'll need to parse 14-bit values.&lt;/p&gt;
&lt;p&gt;To handle one- or two-byte messages, we can define a parser which takes in a function.
This function will take in one or two bytes as parameters and should return a message of the type we want.
For example, to form a ProgramChange message, we may pass in &lt;code&gt;|value| VoiceCategory::ProgramChange { value })&lt;/code&gt;: a lambda function which takes in the 8-bit value and constructs just a ProgramChange instance.
This is all just a convenience so we can snag the one or two bytes we need and invoke a constructor with them.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;one_byte_message&lt;/span&gt;&lt;span&gt;&amp;lt;T, F&amp;gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;f&lt;/span&gt;&lt;span&gt;: F) -&amp;gt; IResult&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], T&amp;gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;where
&lt;/span&gt;&lt;span&gt;    F: Fn(&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;) -&amp;gt; T,
&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(bytes, b) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;take&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;1&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;usize&lt;/span&gt;&lt;span&gt;)(bytes)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((bytes, &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;f&lt;/span&gt;&lt;span&gt;(b[&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;])))
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;two_byte_message&lt;/span&gt;&lt;span&gt;&amp;lt;T, F&amp;gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;f&lt;/span&gt;&lt;span&gt;: F) -&amp;gt; IResult&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], T&amp;gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;where
&lt;/span&gt;&lt;span&gt;    F: Fn(&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;) -&amp;gt; T,
&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(bytes, b) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;take&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;2&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;usize&lt;/span&gt;&lt;span&gt;)(bytes)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((bytes, &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;f&lt;/span&gt;&lt;span&gt;(b[&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;], b[&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;1&lt;/span&gt;&lt;span&gt;])))
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Parsing a 14-bit value is also pretty straightforward, but uses bit manipulation that may be unfamiliar.
We snag two bytes using nom's &lt;code&gt;take&lt;/code&gt; parser, then we shift the first byte left by 7 bits and bitwise or it with the second byte.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;take_14_bit_value&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]) -&amp;gt; IResult&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u16&lt;/span&gt;&lt;span&gt;&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(bytes, db) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;take&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;2&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;usize&lt;/span&gt;&lt;span&gt;)(bytes)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; value &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;((db[&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;as &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u16&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;7&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;|&lt;/span&gt;&lt;span&gt; db[&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;1&lt;/span&gt;&lt;span&gt;] &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;as &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u16&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((bytes, value))
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Okay, now we have our little helpers.
Back to business!&lt;/p&gt;
&lt;h3 id="parsing-voice-messages"&gt;Parsing voice messages&lt;/h3&gt;
&lt;p&gt;As mentioned before, voice messages have two pieces of data in their status byte: the category and the channel.
So the first step in parsing them is to extract that.&lt;/p&gt;
&lt;p&gt;Let's make our parser function, which will take in the status byte and the remaining bytes, and will return a Result (IResult is a nom-specific variant that already includes the error type for us).
To start with, we'll pull out the category and channel from the status byte, and we'll lay out the structure for handling different cases.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;parse_voice_message&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;status_byte&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;remainder&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]) -&amp;gt; IResult&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], VoiceMessage&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; category_nibble &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xf0 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; status_byte;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; channel &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0x0f &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt; status_byte;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(remainder, category) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;match&lt;/span&gt;&lt;span&gt; category_nibble {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="font-style: italic; color: #928374;"&gt;// ...
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((remainder, VoiceMessage::new(category, channel)))
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the question is what we do in each of those cases.
It's easy to handle AfterTouch, ControlCHange, ProgramChange, and ChannelPressure entirely in terms of the helpers we defined before.
The following match arms are to be added inside the &lt;code&gt;match&lt;/code&gt; in the previous code sample&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xa0 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;two_byte_message&lt;/span&gt;&lt;span&gt;(remainder, |&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;note&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pressure&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;            VoiceCategory::AfterTouch { note, pressure }
&lt;/span&gt;&lt;span&gt;        })&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xb0 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;two_byte_message&lt;/span&gt;&lt;span&gt;(remainder, |&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;controller&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;value&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;            VoiceCategory::ControlChange { controller, value }
&lt;/span&gt;&lt;span&gt;        })&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xc0 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;one_byte_message&lt;/span&gt;&lt;span&gt;(remainder, |&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;value&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;            VoiceCategory::ProgramChange { value }
&lt;/span&gt;&lt;span&gt;        })&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xd0 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;one_byte_message&lt;/span&gt;&lt;span&gt;(remainder, |&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pressure&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;            VoiceCategory::ChannelPressure { pressure }
&lt;/span&gt;&lt;span&gt;        })&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we can handle the pitch wheel, which is like these but needs to use the 14-bit parser.
This is our parser for it, which we'll call inside the match as well.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;parse_pitch_wheel&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]) -&amp;gt; IResult&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], VoiceCategory&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(bytes, value) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;take_14_bit_value&lt;/span&gt;&lt;span&gt;(bytes)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((bytes, VoiceCategory::PitchWheel { value }))
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And finally we get to parse voice notes!
Since we have the funky behavior of NoteOn, with velocity=0 denoting sending a NoteOff, we'll use one function for these together.
But we still get to reuse those helpers!
It takes in both the byte slice and a boolean, &lt;code&gt;off&lt;/code&gt;, which is used to say whether this is &lt;em&gt;certainly&lt;/em&gt; a note-off event or not.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;parse_voice_note&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;off&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;bool&lt;/span&gt;&lt;span&gt;) -&amp;gt; IResult&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], VoiceCategory&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;two_byte_message&lt;/span&gt;&lt;span&gt;(bytes, |&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;note&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;velocity&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if&lt;/span&gt;&lt;span&gt; velocity &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;== &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;||&lt;/span&gt;&lt;span&gt; off {
&lt;/span&gt;&lt;span&gt;            VoiceCategory::NoteOff { note, velocity }
&lt;/span&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;else &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;            VoiceCategory::NoteOn { note, velocity }
&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we add the remaining three cases to our match, along with the default case.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0x80 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;parse_voice_note&lt;/span&gt;&lt;span&gt;(remainder, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;true&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0x90 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;parse_voice_note&lt;/span&gt;&lt;span&gt;(remainder, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;false&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xe0 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;parse_pitch_wheel&lt;/span&gt;&lt;span&gt;(remainder)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;_ =&amp;gt; &lt;/span&gt;&lt;span&gt;(remainder, VoiceCategory::Unknown),
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Put it all together, and we get a parser for voice messages!&lt;/p&gt;
&lt;h3 id="parsing-system-common-messages"&gt;Parsing system common messages&lt;/h3&gt;
&lt;p&gt;The last group of messages is a lot of the same, so I'll start with the full definition here and then dive into the interesting part, SysEx messages.
I'm including one unseen function definition in here, &lt;code&gt;parse_song_position_pointer&lt;/code&gt;, since it's the same as the pitch wheel one except it returns a different variant—all the parsing is the same.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;parse_system_common&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;status_byte&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]) -&amp;gt; IResult&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], SystemCommon&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;match&lt;/span&gt;&lt;span&gt; status_byte {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xf0 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;parse_system_exclusive&lt;/span&gt;&lt;span&gt;(bytes),
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xf1 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;one_byte_message&lt;/span&gt;&lt;span&gt;(bytes, |&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;time_code&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;            SystemCommon::MidiTimeCode { time_code }
&lt;/span&gt;&lt;span&gt;        }),
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xf2 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;parse_song_position_pointer&lt;/span&gt;&lt;span&gt;(bytes),
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xf3 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;one_byte_message&lt;/span&gt;&lt;span&gt;(bytes, |&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;song_number&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;            SystemCommon::SongSelect { song_number }
&lt;/span&gt;&lt;span&gt;        }),
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xf6 &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((bytes, SystemCommon::TuneRequest)),
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;_ =&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((bytes, SystemCommon::Unknown)),
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;parse_song_position_pointer&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]) -&amp;gt; IResult&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], SystemCommon&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(remainder, value) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;take_14_bit_value&lt;/span&gt;&lt;span&gt;(bytes)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((remainder, SystemCommon::SongPositionPointer { value }))
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the only thing we haven't seen is &lt;code&gt;parse_system_exclusive&lt;/code&gt;.
Remember that it's a dynamically sized message.
Once we detect the SysEx starting byte (0xf0), we just take and take and take until we find the ending byte, which is 0xf7.
We'll leverage a couple of nom combinators for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;take_till&lt;/code&gt; accepts a function as an argument and takes bytes until that function returns true&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tag&lt;/code&gt; accepts a string or byte array and expects that to be the next value, failing if it isn't present&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And then we can shove this all into a Vec and call it a day.&lt;/p&gt;
&lt;p&gt;Putting it together, we get this short function.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;parse_system_exclusive&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;bytes&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;]) -&amp;gt; IResult&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;[&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;], SystemCommon&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(remainder, data) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;take_till&lt;/span&gt;&lt;span&gt;(is_status_byte)(bytes)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let &lt;/span&gt;&lt;span&gt;(remainder, &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;_&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;tag&lt;/span&gt;&lt;span&gt;([&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0xf7&lt;/span&gt;&lt;span&gt;])(remainder)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; data: &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u8&lt;/span&gt;&lt;span&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span&gt; data.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;into&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;((remainder, SystemCommon::SystemExclusive { data }))
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's it, we've handled the system common messages!&lt;/p&gt;
&lt;p&gt;Which means we've handled &lt;em&gt;all&lt;/em&gt; the message types.&lt;/p&gt;
&lt;h1 id="can-i-use-it"&gt;Can I use it?&lt;/h1&gt;
&lt;p&gt;This code is for a project I'm working on and I don't have that open source yet (it probably will be eventually!), so no (or not yet).
Besides, you probably don't want to: it hasn't been profiled for performance, it's only tested against two of my instruments, and it's likely to have breaking changes soon.
There are other libraries out there that are a better choice if you do want to use them, such as &lt;a href="https://crates.io/crates/midi-msg"&gt;midi-msg&lt;/a&gt; and &lt;a href="https://crates.io/crates/midly"&gt;midly&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It's also a pretty simple protocol, and it's fun to build your own small parsers!&lt;/p&gt;
&lt;p&gt;If you've done anything fun with MIDI or Rust, I'd love to hear about it.
Just send me an email (listed below).&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Thank you to &lt;a href="https://alanza.xyz/"&gt;Robbie&lt;/a&gt; for very helpful feedback and corrections, including the note about CV/gate. Any remaining errors are my own.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-cv"&gt;
&lt;p&gt;There was already a method for analog interfacing between equipment called &lt;a href="https://en.wikipedia.org/wiki/CV/gate"&gt;CV/gate&lt;/a&gt;.
This was around at least as early as 1970, but I can't find a lot of info on this. &lt;a href="https://ntietz.com/blog/parsing-midi-rust/#fr-cv-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-bits"&gt;
&lt;p&gt;This means you can do a bitwise &lt;code&gt;&amp;amp; 0x80&lt;/code&gt; to check if something is a status or data byte. &lt;a href="https://ntietz.com/blog/parsing-midi-rust/#fr-bits-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-but-also"&gt;
&lt;p&gt;There are also channel mode messages, which are a variation on a particular kind of voice message.
I'm leaving them out here for clarity, but they do technically exist. &lt;a href="https://ntietz.com/blog/parsing-midi-rust/#fr-but-also-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-hex"&gt;
&lt;p&gt;I'll represent everything that is hexadecimal with 0x at the start. &lt;a href="https://ntietz.com/blog/parsing-midi-rust/#fr-hex-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 09 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/parsing-midi-rust/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Grafana with VictoriaLogs plugin on NixOS</title><link>https://paul.totterman.name/posts/grafana-victorialogs-nixos/</link><description>&lt;p&gt;&lt;a href="https://docs.victoriametrics.com/victorialogs/" rel="noopener" target="_blank"&gt;VictoriaLogs&lt;/a&gt; looks like an
interesting alternative to Grafana Loki. But it would be nice to still query it
via the Grafana Dashboard along with all the rest. It takes a couple of specific
steps to install the appropriate plugin, especially on NixOS.&lt;/p&gt;</description><author>Paul's page</author><pubDate>Mon, 09 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://paul.totterman.name/posts/grafana-victorialogs-nixos/</guid></item><item><title>Why I read history books</title><link>https://bytepawn.com/beugras-kiugras-why-i-read-history-books.html</link><description>&lt;p&gt;Hungary’s rushed entry into World War II and its botched attempt to break away from it show how stale leadership trapped in its own fantasy bubble can steer a nation toward disaster. By “political backtesting” these moments — comparing their underlying factors to the present — today’s citizens can weigh future outcomes, draw practical lessons, we can make more informed decisions about where and how to live.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Kiugras Beugras" src="/images/kiugras-beugras.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Mon, 09 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/beugras-kiugras-why-i-read-history-books.html</guid></item><item><title>Justin Timberlake: The Forget Tomorrow Tour</title><link>https://digitalnomadder.micro.blog/2024/12/08/justin-timberlake-the.html</link><description>&lt;img alt="" height="225" src="https://cdn.uploads.micro.blog/79953/2024/1567b811ee.jpg" width="225" /&gt;
&lt;p&gt;We went to see Justin Timberlake at the Kia Center the day after coming back from &lt;a href="https://digitalnomadder.micro.blog/2024/12/02/an-anxiety-attack.html"&gt;Daytona&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Not much to say.  The visuals were great. The choreography was top notch and he put on a good performance.&lt;/p&gt;</description><author>The Digital Nomad</author><pubDate>Sun, 08 Dec 2024 23:45:48 GMT</pubDate><guid isPermaLink="true">https://digitalnomadder.micro.blog/2024/12/08/justin-timberlake-the.html</guid></item><item><title>Första gången fast på tåget från Uppsala och Anki för svenskainlärning</title><link>https://liza.io/f%C3%B6rsta-g%C3%A5ngen-fast-p%C3%A5-t%C3%A5get-fr%C3%A5n-uppsala-och-anki-f%C3%B6r-svenskainl%C3%A4rning/</link><description>&lt;p&gt;Så det hände äntligen: jag fastnade på tåget från Uppsala till Stockholm C. Min kollega från Uppsala har redan retat mig, eftersom han visste att det skulle hända. Han hade rätt.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Sun, 08 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/f%C3%B6rsta-g%C3%A5ngen-fast-p%C3%A5-t%C3%A5get-fr%C3%A5n-uppsala-och-anki-f%C3%B6r-svenskainl%C3%A4rning/</guid></item><item><title>Optimizing mastodon for a single user</title><link>https://blog.ovalerio.net/archives/3044</link><description>I&amp;#8217;ve been participating in the Fediverse through my own mastodon instance since 2017. What started as an experiment to test new things, focused on exploring decentralized and federated alternatives for communicating on top of the internet, stuck. At the end of 2024, I&amp;#8217;m still there. The rhetoric on this network is that you should find [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Sun, 08 Dec 2024 20:16:40 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/3044</guid></item><item><title>IdentityServer in Docker Containers: HTTPS and SameSite (Part 3)</title><link>https://nestenius.se/net/identityserver-in-docker-containers-part-3/</link><description>&lt;p&gt;In this third part of the series, we tackle login issues in IdentityServer caused by cookie restrictions in HTTP and show how to resolve them by implementing HTTPS. We’ll guide you through securing communication between the host, client, and IdentityServer containers and configuring HTTPS in Docker to ensure everything runs smoothly. This blog has been [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/net/identityserver-in-docker-containers-part-3/"&gt;IdentityServer in Docker Containers: HTTPS and SameSite (Part 3)&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Sun, 08 Dec 2024 19:54:27 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/net/identityserver-in-docker-containers-part-3/</guid></item><item><title>"All your base are belong to us" introduction to my 2004 MIT Spam Conference talk</title><link>http://blog.jgc.org/2024/12/all-you-base-are-belong-to-us.html</link><description>&lt;p&gt;As I've mentioned before &lt;a href="https://blog.jgc.org/2023/07/how-to-beat-adaptivebayesian-spam.html"&gt;my talk at the 2004 MIT Spam Conference&lt;/a&gt; was about using one machine learning system to beat another. In that case a spammer using machine learning to beat a machine learning spam filter.&lt;/p&gt;&lt;p&gt;The talk started with a short video playing off the &lt;a href="https://en.wikipedia.org/wiki/All_your_base_are_belong_to_us"&gt;All your base are belong to us&lt;/a&gt; meme. I very carefully edited the images to change the text to be about spam and incorporate Paul Graham (who was the instigator of the MIT Spam Conference and an &lt;a href="https://paulgraham.com/spam.html"&gt;early machine learning spam filter pioneer&lt;/a&gt;).&amp;nbsp;&lt;/p&gt;&lt;p&gt;I thought I'd &lt;a href="https://blog.jgc.org/2023/05/bringing-popfile-web-site-back-from-dead.html"&gt;lost the audio of that introduction&lt;/a&gt; and randomly came across it today. So here is the restored introduction to the talk made on a Mac in 2004 (I still haven't found the audio of the actual talk).&lt;/p&gt;

&lt;div style="padding-top: 56.25%;"&gt;
  &lt;/div&gt;</description><author>John Graham-Cumming's blog</author><pubDate>Sun, 08 Dec 2024 19:28:43 GMT</pubDate><guid isPermaLink="true">http://blog.jgc.org/2024/12/all-you-base-are-belong-to-us.html</guid></item><item><title>When Game Days go wrong</title><link>/game-days-go-wrong/</link><description>&lt;p&gt;It was the week before pandemic lockdowns began.&lt;/p&gt;

&lt;p&gt;Like many companies, we were thinking about what a fully remote workforce might
mean for us. In an attempt to get ahead of things, we scheduled a &lt;a href="https://incident.io/blog/game-day"&gt;‘Game
Day’&lt;/a&gt; to test our work-from-home incident response capabilities. You
know, just in case we’d need to abandon the office (spoiler: we would).&lt;/p&gt;

&lt;p&gt;The timing was perfect. We had several new team members and others who were
almost ready to join the on-call rotation but needed a confidence boost. Our
goal was simple: familiarise people with our systems and processes, deliberately
steering away from stress testing the infrastructure itself.&lt;/p&gt;

&lt;p&gt;Focusing on how we’d respond remotely was the point of this, so we planned to
break things in obvious ways and watch how the incident response unfolded.&lt;/p&gt;

&lt;p&gt;As the designated villain for the day, I had a straightforward plan:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Detach a disk from our Postgres VM (running in a three-node cluster using
&lt;a href="https://github.com/sorintlab/stolon"&gt;Stolon&lt;/a&gt; and &lt;a href="https://etcd.io/"&gt;Etcd&lt;/a&gt;)&lt;/li&gt;
  &lt;li&gt;Fill up an &lt;a href="https://www.elastic.co/elasticsearch"&gt;Elasticsearch&lt;/a&gt; disk with junk (running in Kubernetes)&lt;/li&gt;
  &lt;li&gt;Create tons of consoles to exhaust cluster capacity&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pretty standard stuff. In fact, it sounded like it might even be fun.&lt;/p&gt;

&lt;h2 id="all-according-to-plan"&gt;All according to plan&lt;/h2&gt;

&lt;p&gt;For a while it was, with the Game Day starting smoothly.&lt;/p&gt;

&lt;p&gt;When Postgres died and recovered, the team opened an incident and handled it
well. As they were wrapping up the Postgres recovery, I started filling up the
Elasticsearch disk, hoping to nudge the fairly large (~30TB) cluster into a
degraded state.&lt;/p&gt;

&lt;p&gt;Alerts began firing, and the team efficiently split into two groups to handle
both issues. Perfect! This is exactly the type of response we were after,
proving we could handle ramping pressure even while remote.&lt;/p&gt;

&lt;p&gt;It was time, then, to make things more interesting. So I started creating
a load of consoles in the Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;The console creation was meant to gradually fill the Kubernetes cluster with
batch jobs, exhausting our staging clusters capacity. This should have been safe
– we had &lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/pod-priority-preemption/"&gt;pod priorities&lt;/a&gt; ensuring staging workloads were
assigned much lower priorities than production ones. No matter how many staging
consoles were created in the cluster there should be no impact on production
workloads, as Kubernetes will evict staging over production workloads first.&lt;/p&gt;

&lt;p&gt;So when someone from support raised the alarm that the production app was down,
I was more than a little confused.&lt;/p&gt;

&lt;h2 id="going-sideways"&gt;Going sideways&lt;/h2&gt;

&lt;p&gt;It was at this point that everything started going sideways.&lt;/p&gt;

&lt;p&gt;Alerts were firing everywhere: Postgres alerts, HAProxy reporting no backends,
blackbox alerts confirming our API was unresponsive, and – most worryingly –
Etcd cluster out of quorum. That last one was particularly problematic because
Stolon relies on Etcd for leadership election. No Etcd quorum means no database
connections, which means no API requests, no batch jobs… no nothing.&lt;/p&gt;

&lt;p&gt;The root cause? A perfect storm of “tomorrow’s problems” catching up with us.&lt;/p&gt;

&lt;p&gt;At the time, we were running everything (production and staging) in a single
large Kubernetes cluster, separating environments and cluster infrastructure
using namespaces. We used pod priorities to ensure workloads would arrange
themselves correctly – cluster-level infrastructure above production, production
above staging, and so on.&lt;/p&gt;

&lt;p&gt;But we’d made a critical mistake, one that would come back to haunt us in the
worst possible way. It all traced back to when we initially rolled out pod
priorities…&lt;/p&gt;

&lt;h2 id="the-mistake"&gt;The mistake&lt;/h2&gt;

&lt;p&gt;When we initially rolled out pod priorities, we thought we’d be physically
separating the cluster within a month. To avoid doing substantial operational
work on something that would be rebuilt “soon,” we’d skipped applying pod
priorities to some sensitive deployments, including Etcd.&lt;/p&gt;

&lt;p&gt;Here’s where things get interesting: in Kubernetes, pod priorities work through
a numeric class system. We had set this up pretty sensibly, with system
workloads at priority 300, production at 200, and staging at 100. The idea is
simple – when the cluster needs to make space, it evicts pods with lower
priorities first.&lt;/p&gt;

&lt;p&gt;But there’s a subtle gotcha that bit us hard. Before you introduce any priority
classes to your cluster, every pod essentially has equal priority. They’re all
implicitly set to 0, but it doesn’t matter because they’re all the same. The
moment you add even a single priority class, though, that changes completely –
now any pod without an explicit priority gets assigned 0, making it lower
priority than everything else.&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;There exists a ‘globalDefault’ property that can be set for a priority class
which means any pod without a class will get assigned that priority. We didn’t
have this set in our cluster, though I’d advise people set this if they’re
using pod priorities.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;By not setting a priority on our Etcd pods while having priorities everywhere
else, we’d accidentally marked our most critical infrastructure component as the
first thing to be evicted under pressure. When I came along and began filling
the cluster with staging consoles (a job I admit to doing with enthusiasm), we
started evicting Etcd pods. By the time alerts fired, we’d evicted enough pods
to lose quorum, having eaten well into our &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/disruptions/"&gt;pod disruption
budgets&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This prevented the Etcd pods from successfully rejoining the cluster when
restarted. We were, in technical terms, pretty screwed.&lt;/p&gt;

&lt;h2 id="righting-the-ship"&gt;Righting the ship&lt;/h2&gt;

&lt;p&gt;The immediate focus was restoring database access. We deactivated the Stolon
Postgres cluster manager and booted Postgres manually, relying on muscle memory
for the right commands. We bypassed both PgBouncer and the Stolon fencing proxy,
pointing applications directly at Postgres. This brought things back up, albeit
in an unmanaged state.&lt;/p&gt;

&lt;p&gt;After taking a moment to contemplate our life choices, we formulated a plan
using Etcd’s disaster recovery procedure. It took about six hours to bring
everything back and perform a Stolon failover. A good chunk of that time was
spent running &lt;code class="language-plaintext highlighter-rouge"&gt;pg_basebackup&lt;/code&gt; to restore another node – our database was about
6TB at this point.&lt;/p&gt;

&lt;p&gt;It was a long day and not at all what we’d had planned. As drills go though,
this was probably the most intense way we could ever have tested our production
readiness in a remote context, even if it cost us more than we’d anticipated.&lt;/p&gt;

&lt;h2 id="infrastructure-debt-doesnt-age-well"&gt;Infrastructure debt doesn’t age well&lt;/h2&gt;

&lt;p&gt;There’s a pattern in infrastructure work that’s worth recognising: we often
defer work because we think a better solution is coming “soon.” But “soon” has a
way of stretching into weeks or months, and the work we carefully documented and
planned to revisit gets buried under newer, more urgent tasks.&lt;/p&gt;

&lt;p&gt;Over time, this creates hidden trapdoors in our infrastructure. When we leave
configuration half-applied or work partially complete, we’re not just creating
technical debt – we’re fragmenting our team’s mental model of the system. Every
“we’ll fix it later” adds another dimension of complexity, often temporal, that
someone needs to keep in mind. Eventually, someone (in this case, me) will
forget about that complexity and fall right through the trapdoor.&lt;/p&gt;

&lt;h2 id="the-real-test-isnt-the-outage"&gt;The real test isn’t the outage&lt;/h2&gt;

&lt;p&gt;Of everything I took from this incident, though, it was how our organisation
responded that I found most interesting.&lt;/p&gt;

&lt;p&gt;In preparing for an important change (remote work during COVID), while trying to
do the right thing for the company (training new incident responders), we
accidentally caused a significant outage. It would have been easy to say “well,
we won’t be doing Game Days again!” or worse, fire the person (me) who pressed
the buttons and wash your hands of their incomptence (ouch).&lt;/p&gt;

&lt;p&gt;Instead, my boss, our CTO, caught up with me afterward to say “thank god we
found this while the whole team were on a call ready to respond!”. It’s true,
this would have been far worse had it happened out of hours, but you bet he’d
got a lot of heat for this outage. It would have been easy to push blame to me,
but he didn’t, and in that moment he created the essential conditions of a
‘blameless’ incident culture. One where an outage like this can balance its cost
with the lessons learned from it.&lt;/p&gt;

&lt;p&gt;Sometimes the most valuable lessons come from the most uncomfortable situations.
The trick is making sure your team feels safe enough to learn from them.&lt;/p&gt;</description><author>Lawrence Jones</author><pubDate>Sun, 08 Dec 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">/game-days-go-wrong/</guid></item><item><title>Three ways to shoot yourself in the foot with Google Cloud Run</title><link>https://philbooth.me/blog/three-ways-to-shoot-yourself-in-the-foot-with-cloud-run/</link><description>&lt;p&gt;This post is an expansion of
a &lt;a href="https://news.ycombinator.com/item?id=42253665"&gt;comment I made on HN&lt;/a&gt; recently
and continues my &lt;a href="/blog?topic=extreme-learning"&gt;&lt;em&gt;Extreme Learning&lt;/em&gt;&lt;/a&gt; series.
Previously I discussed how I&amp;rsquo;ve managed to break production
&lt;a href="/blog/four-ways-to-shoot-yourself-in-the-foot-with-redis"&gt;with Redis&lt;/a&gt;,
&lt;a href="/blog/nine-ways-to-shoot-yourself-in-the-foot-with-postgresql"&gt;with PostgreSQL&lt;/a&gt;
and &lt;a href="/blog/six-ways-to-shoot-yourself-in-the-foot-with-healthchecks"&gt;with healthchecks&lt;/a&gt;.
Now I&amp;rsquo;ll show you how I did it with &lt;a href="https://cloud.google.com/run?hl=en"&gt;Cloud Run&lt;/a&gt; too.&lt;/p&gt;
&lt;p&gt;For context,
we migrated from &lt;a href="https://cloud.google.com/compute/docs/instance-groups"&gt;Managed Instance Groups&lt;/a&gt;
to Cloud Run
at work last year.
Cloud Run promises to simplify your production infrastructure
and mostly we found that to be true.
But we also discovered some hidden gotchas
that can catch you out if you&amp;rsquo;re not paying attention.&lt;/p&gt;
&lt;h3 id="1-use-websockets-without-changing-the-default-request-timeout"&gt;1. Use websockets without changing the default request timeout&lt;/h3&gt;
&lt;p&gt;By default,
&lt;a href="https://cloud.google.com/run/docs/configuring/request-timeout"&gt;Cloud Run terminates inbound TCP connections after 5 minutes&lt;/a&gt;.
If you&amp;rsquo;re doing anything that uses a long-running connection,
communicating via a websocket for example,
you&amp;rsquo;ll want to change that setting.
Otherwise at best you&amp;rsquo;ll have lots of reconnection overhead
and at worst you&amp;rsquo;ll have weird bugs in production
that nobody can reproduce in local.&lt;/p&gt;
&lt;p&gt;The upper limit on connections in Cloud Run is one hour,
so you&amp;rsquo;ll need proper reconnection logic on clients
if you&amp;rsquo;re running longer than that.
(but you should have proper reconnection logic regardless of course,
because you&amp;rsquo;re working over the internet)&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re changing this setting in the GCP web console,
it should look like this:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the request timeout setting in GCP" src="https://assets.philbooth.me/images/cloud-run-request-timeout.png" /&gt;&lt;/p&gt;
&lt;p&gt;Or if you&amp;rsquo;re using Terraform,
you should set &lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#timeout-1"&gt;&lt;code&gt;timeout = &amp;quot;3600s&amp;quot;&lt;/code&gt;&lt;/a&gt;
in the &lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service"&gt;&lt;code&gt;cloud_run_v2_service&lt;/code&gt; resource&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="2-ignore-the-difference-between-first-and-second-generation-environments"&gt;2. Ignore the difference between first and second generation environments&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://cloud.google.com/run/docs/about-execution-environments"&gt;Cloud Run has two separate execution environments&lt;/a&gt;,
each with its own tradeoffs.
The first generation environment emulates Linux (imperfectly)
and has faster cold starts.
The second generation runs on real Linux
and has faster CPU and faster network throughput.
If you don&amp;rsquo;t specify a choice,
it defaults to first generation.&lt;/p&gt;
&lt;p&gt;For our part,
we valued faster network throughput
so opted for second generation.
That setting looks like this in the web console:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the execution environment setting in GCP" src="https://assets.philbooth.me/images/cloud-run-execution-environment.png" /&gt;&lt;/p&gt;
&lt;p&gt;For terraform we set
&lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#execution_environment-1"&gt;&lt;code&gt;execution_environment = &amp;quot;EXECUTION_ENVIRONMENT_GEN2&amp;quot;&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="3-dont-pay-attention-to-autoscaling-settings"&gt;3. Don&amp;rsquo;t pay attention to autoscaling settings&lt;/h3&gt;
&lt;p&gt;With the default settings in place,
&lt;a href="https://cloud.google.com/run/docs/about-instance-autoscaling"&gt;Cloud Run will autoscale up when CPU usage reaches 60%
and scale down to zero when there&amp;rsquo;s no traffic&lt;/a&gt;.
You may want to change both of those
depending on your usage.&lt;/p&gt;
&lt;p&gt;In our application,
CPU usage turned out to be a suboptimal metric for scaling up,
so we tweaked the maximum number of concurrent requests to compensate.
We also have periods of very low activity at weekends,
so set the minimum instance count to 1 to eliminate cold starts.&lt;/p&gt;
&lt;p&gt;Here are those settings in the web console:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the maximum concurrent requests setting in GCP" src="https://assets.philbooth.me/images/cloud-run-concurrent-requests.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot of the minimum instances setting in GCP" src="https://assets.philbooth.me/images/cloud-run-minimum-instances.png" /&gt;&lt;/p&gt;
&lt;p&gt;In terraform those settings are
&lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#max_instance_request_concurrency-1"&gt;&lt;code&gt;max_instance_request_concurrency = 50&lt;/code&gt;&lt;/a&gt;
and &lt;a href="https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/cloud_run_v2_service#min_instance_count-1"&gt;&lt;code&gt;scaling { min_instance_count = 1 }&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;</description><author>Phil Booth's Blog</author><pubDate>Sun, 08 Dec 2024 12:00:00 GMT</pubDate><guid isPermaLink="true">https://philbooth.me/blog/three-ways-to-shoot-yourself-in-the-foot-with-cloud-run/</guid></item><item><title>Improved weather forecasts</title><link>https://ilearnt.com/blog/weatherforecasts/</link><description>&lt;p&gt;At the moment we are deep in the clutches of Storm Darragh as it batters the UK. We have currently lost one fence panel and there may be more casualties. I looks like I will get to experience the &amp;ldquo;joy&amp;rdquo; soon of watching my son play football in high winds and torrential rain.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Sun, 08 Dec 2024 10:37:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/weatherforecasts/</guid></item><item><title>Holding Space for Wicked (Part One)</title><link>https://www.swyx.io/wicked</link><author>swyx's site RSS Feed</author><pubDate>Sun, 08 Dec 2024 04:57:10 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/wicked</guid></item><item><title>Embedding Vectors vs. Vector Embeddings</title><link>https://tanelpoder.com/posts/embedding-vectors-vs-vector-embeddings/</link><description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: I&amp;rsquo;m not an ML expert and not even a serious ML specialist (yet?), so feel free to let me know if I&amp;rsquo;m wrong!&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It seems to me that we have hit a bit of an &amp;ldquo;on-premises&amp;rdquo; vs. &amp;ldquo;on-premise&amp;rdquo; situation in the ML/AI and vector search terminology space.  The majority of product announcements, blog articles and even some papers I&amp;rsquo;ve read use the term &lt;strong&gt;&lt;em&gt;vector embeddings&lt;/em&gt;&lt;/strong&gt; to describe embeddings, but embeddings &lt;em&gt;already are vectors&lt;/em&gt; themselves!&lt;/p&gt;</description><author>Tanel Poder Blog</author><pubDate>Sun, 08 Dec 2024 03:38:11 GMT</pubDate><guid isPermaLink="true">https://tanelpoder.com/posts/embedding-vectors-vs-vector-embeddings/</guid></item><item><title>Humidity and ratios</title><link>https://blog.harterrt.com/humidity.html</link><description>&lt;p&gt;My house gets super dry in the winter and I didn't understand why.&lt;/p&gt;
&lt;p&gt;My weather station says it's drier inside than outside,
which didn't make a ton of sense. 
I'm cooking, and sweating, and showering inside.
It should be &lt;em&gt;more&lt;/em&gt; humid.&lt;/p&gt;
&lt;p&gt;I suspected my forced hot air system was to …&lt;/p&gt;</description><author>blog.harterrt.com</author><pubDate>Sun, 08 Dec 2024 03:24:00 GMT</pubDate><guid isPermaLink="true">https://blog.harterrt.com/humidity.html</guid></item><item><title>Small Web</title><link>https://robkohr.com/articles/small-web</link><description>Small Web</description><author>RobKohr's Blog</author><pubDate>Sun, 08 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/small-web</guid></item><item><title>Retail Returns</title><link>https://www.craigpardey.com/post/2024-12-08-retail-returns/</link><description>&lt;p&gt;I recently had to return an item that I&amp;rsquo;d bought online. It&amp;rsquo;s free to return the item in-person to one of their retail locations, or $10 to ship it back. There was a store nearby so I elected to do an in-person return but the experience shows what can happen when software and processes are developed without involving the user.&lt;/p&gt;
&lt;p&gt;The store clerk was friendly and helpful. He asked for my online order number, found it in the system, and told me that the money would be refunded to the original form of payment used for the purchase - in this case, PayPal. Everything is going smoothly to this point.&lt;/p&gt;</description><author>Craig Pardey</author><pubDate>Sun, 08 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.craigpardey.com/post/2024-12-08-retail-returns/</guid></item><item><title>I Photographed Santa Skivvies Run 2024</title><link>https://thomashunter.name/posts/2024-12-08-santa-skivvies-run</link><author>Thomas Hunter II</author><pubDate>Sun, 08 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://thomashunter.name/posts/2024-12-08-santa-skivvies-run</guid></item><item><title>Bash script to switch Pulseaudio sinks</title><link>https://tomk32.de/2024/12/08/bash-script-to-switch-pulseaudio-sinks.html</link><description>Who doesn’t love using the keyboard even for simple things like switching the output (or sink as pulseaudio calls it) for you audio? My setup is a set of external speakers, a monitor with rather bad speakers and plugged into the monitor at all time my headphones. That makes three sinks which makes my script a bit more complicated than a simple toggle to the other (i.e. IDLE sink) would be.</description><author>Thomas R. Koll</author><pubDate>Sun, 08 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://tomk32.de/2024/12/08/bash-script-to-switch-pulseaudio-sinks.html</guid></item><item><title>Disable Slog Messages in Go Tests</title><link>https://boyter.org/posts/golang-slog-disable-tests/</link><description>&lt;p&gt;As much as I like &lt;a href="https://github.com/rs/zerolog"&gt;ZeroLog&lt;/a&gt; for Go logging Slog being added to the standard library fits in with my rule of avoiding 3rd party packages when they are not entirely required hence I have started moving anything I work on over to it.&lt;/p&gt;
&lt;p&gt;One thing I find especially annoying however is log messages inside tests. As such here is a quick code snippet which turns off all slog outputs, but only for tests. Stick it into any &lt;code&gt;_test.go&lt;/code&gt; file to disable logging outputs for that package.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Sun, 08 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/golang-slog-disable-tests/</guid></item><item><title>objc async selectors</title><link>https://whackylabs.com/swift/objc/async/2024/12/07/objc-async-selectors/</link><description>&lt;p&gt;I very recently learned it the hard way that objc selectors can not be async.&lt;/p&gt;

&lt;p&gt;In simple words, this will crash at runtime:&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;buttonFrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This is a button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;#selector(&lt;/span&gt;&lt;span class="nf"&gt;handleTap&lt;/span&gt;&lt;span class="kd"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
  &lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;touchUpInside&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
  
&lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleTap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle Tap"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;No warnings, no errors, no messages on console, nothing. Just crash!&lt;/p&gt;

&lt;p&gt;As it turns out all objc selector marked as async will crash. For example:&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;#selector(&lt;/span&gt;&lt;span class="nf"&gt;handleTap&lt;/span&gt;&lt;span class="kd"&gt;)&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So what is going on here. Let’s first print the selector generated by the compiler for &lt;code class="language-plaintext highlighter-rouge"&gt;async&lt;/code&gt; methods.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nf"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;NSStringFromSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;#selector(&lt;/span&gt;&lt;span class="nf"&gt;handleTap&lt;/span&gt;&lt;span class="kd"&gt;)&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;// "handleTapWithCompletionHandler:"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;As per swift objc interops guidelines, Swift maps all objc methods with suffix &lt;code class="language-plaintext highlighter-rouge"&gt;"WithCompletionHandler"&lt;/code&gt; as `async. So these two are equivalent:&lt;/p&gt;

&lt;div class="language-objc highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;stopRecordingWithCompletionHandler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="n"&gt;_Nullable&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;RPPreviewViewController&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;_Nullable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;NSError&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;_Nullable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;stopRecording&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;throws&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;RPPreviewViewController&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So our &lt;code class="language-plaintext highlighter-rouge"&gt;func handleTap()&lt;/code&gt; becomes &lt;code class="language-plaintext highlighter-rouge"&gt;- (void) handleTapWithCompletionHandler:(CompletionHandler completionHandler)&lt;/code&gt; in objc.&lt;/p&gt;

&lt;p&gt;The next question naturally is what is the type of &lt;code class="language-plaintext highlighter-rouge"&gt;CompletionHandler&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;I tried to search online if others have also faced this problem. And yes there are quite a number of discussions on this topic. 
&lt;a href="https://github.com/swiftlang/swift/issues/60084#issuecomment-1192174616"&gt;This one&lt;/a&gt; even has nice a workaround to the problem:&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@convention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"completion handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;#selector(&lt;/span&gt;&lt;span class="nf"&gt;handleTap&lt;/span&gt;&lt;span class="kd"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;completion&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// prints:&lt;/span&gt;
&lt;span class="c1"&gt;// completion handler&lt;/span&gt;
&lt;span class="c1"&gt;// handle Tap&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;@convention&lt;/code&gt; is a attribute provided by swift for calling conventions in Swift. And &lt;code class="language-plaintext highlighter-rouge"&gt;@convention(block)&lt;/code&gt; is the attribute to represent Objective-C blocks as Swift closures.&lt;/p&gt;

&lt;p&gt;Now, with all this acquired knowledge can we fix our crash? We still can’t use the &lt;code class="language-plaintext highlighter-rouge"&gt;#selector(handleTap)&lt;/code&gt; for target action pattern because that pattern is very well documented to support only following method signatures:&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;doSomething&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;forEvent&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here the &lt;code class="language-plaintext highlighter-rouge"&gt;sender&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;event&lt;/code&gt; is autofilled by objc runtime while invoking the provided &lt;code class="language-plaintext highlighter-rouge"&gt;selector&lt;/code&gt;. And as you can see there is no place to provide arbitrary arguments. But for this entire mechanism to work we want the &lt;code class="language-plaintext highlighter-rouge"&gt;sender&lt;/code&gt; to be of type &lt;code class="language-plaintext highlighter-rouge"&gt;@convention(block) () -&amp;gt; Void&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So we can create our own custom &lt;code class="language-plaintext highlighter-rouge"&gt;UIControl&lt;/code&gt; subclass and hijack the target-action mechanism to update the sender. 
In other words, we need to override the &lt;code class="language-plaintext highlighter-rouge"&gt;sendAction()&lt;/code&gt; method and then call the &lt;code class="language-plaintext highlighter-rouge"&gt;performSelector&lt;/code&gt;.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;MyButton&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIButton&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;sendAction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Selector&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;?,&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIEvent&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;@convention&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Void&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"completion handler"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;NSObject&lt;/span&gt;&lt;span class="p"&gt;)?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;with&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;buttonFrame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;MyButton&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;buttonFrame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitleColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This is a button"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;normal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;button&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTarget&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;#selector(&lt;/span&gt;&lt;span class="nf"&gt;handleTap&lt;/span&gt;&lt;span class="kd"&gt;)&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;for&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;touchUpInside&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  
  &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleTap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Task&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;debugPrint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"handle Tap"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that is how you can have a button action as &lt;code class="language-plaintext highlighter-rouge"&gt;async&lt;/code&gt;.&lt;/p&gt;

&lt;h3 id="references"&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/responding-to-control-based-events-using-target-action"&gt;https://developer.apple.com/documentation/uikit/responding-to-control-based-events-using-target-action&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/uicontrol/sendaction(_:to:for:)"&gt;https://developer.apple.com/documentation/uikit/uicontrol/sendaction&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://reintech.io/blog/using-swifts-convention"&gt;https://reintech.io/blog/using-swifts-convention&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://github.com/swiftlang/swift/issues/60084"&gt;https://github.com/swiftlang/swift/issues/60084&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://github.com/swiftlang/swift-evolution/blob/main/proposals/0297-concurrency-objc.md"&gt;https://github.com/swiftlang/swift-evolution/blob/main/proposals/0297-concurrency-objc.md&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes/#convention"&gt;https://docs.swift.org/swift-book/documentation/the-swift-programming-language/attributes/#convention&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Sat, 07 Dec 2024 16:21:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/swift/objc/async/2024/12/07/objc-async-selectors/</guid></item><item><title>Pico BLE Vape Remote</title><link>https://www.xythobuz.de/ble_remote.html</link><description>&lt;p&gt;Like many modern devices, some of the medical vaporizer devices from S&amp;amp;B (the &lt;a href="https://www.storz-bickel.com/de/crafty-plus-c"&gt;Crafty&lt;/a&gt;, &lt;a href="https://www.storz-bickel.com/de/venty"&gt;Venty&lt;/a&gt; and the &lt;a href="https://www.storz-bickel.com/de/volcanohybrid"&gt;Volcano Hybrid&lt;/a&gt;) offer Bluetooth connectivity and &lt;a href="https://app.storz-bickel.com/"&gt;an app&lt;/a&gt; to control them.&lt;/p&gt;
&lt;p&gt;Unfortunately the official mobile apps have / had some problems.
The iOS version is no longer available after Apple decided to remove "drug related" Apps.
So understandably the company no longer developed either their iOS or Android apps, instead releasing a web app that uses the Chrome Bluetooth APIs.&lt;/p&gt;
&lt;p&gt;Yet all of these variants are problematic in different ways.
The Android app offers customizable workflows for the Volcano Hybrid.
But the Bluetooth implementation is very wonky, sometimes simply missing steps in the workflow.
And because there's no error checking, the workflow simply continues in a broken state.
The webapp doesn't have this problem, but instead the workflows are no longer customizable, instead just offering a small number of hard-coded pre-defined workflows.&lt;/p&gt;
&lt;p&gt;This is not satisfying, of course, and also presented itself as a great opportunity to play around with BLE.&lt;/p&gt;
&lt;p&gt;Fortunately the official web app is made with non-minimized an unobfuscated JavaScript, so the BLE protocol for the different devices can easily be reverse engineered.
I wrote a small &lt;a href="https://codeberg.org/xythobuz/Volcano-Remote/src/branch/master/web-app/fetch.sh"&gt;script&lt;/a&gt; that helps with downloading and beautifying the official sources.&lt;/p&gt;
&lt;p&gt;As a first attempt I implemented the Volcano workflow functionality in Python on the PC.
I had some problems, so I made two implementations, with &lt;a href="https://bleak.readthedocs.io/en/latest/"&gt;bleak&lt;/a&gt; and &lt;a href="https://simpleble.readthedocs.io/en/latest/simplepyble/usage.html"&gt;SimplePyBLE&lt;/a&gt;.
Turns out my difficulties came from having multiple bluetooth adapters on my Linux machine.
Now the scripts both work fine for me.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/volcano_remote_pc_2.jpg"&gt;&lt;img alt="Volcano Remote script in action (old version)" class="pic" src="https://www.xythobuz.de/img/volcano_remote_pc_2_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/volcano_remote_pc_1.png"&gt;&lt;img alt="Volcano Remote script in action (current version)" class="pic" src="https://www.xythobuz.de/img/volcano_remote_pc_1_small.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Then I implemented the same thing on a &lt;a href="https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#raspberry-pi-pico-w-and-pico-wh"&gt;Raspberry Pi Pico W&lt;/a&gt;, with a &lt;a href="https://www.waveshare.com/wiki/Pico-LCD-1.3"&gt;Waveshare Pico LCD 1.3&lt;/a&gt; display.
I first did this in MicroPython but ran into issues as well.
It works fine but I couldn't include all the functionality I wanted.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/volcano_remote_micropython.jpg"&gt;&lt;img alt="Volcano Remote MicroPython prototype" class="pic" src="https://www.xythobuz.de/img/volcano_remote_micropython_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;So I made a fourth iteration, this time in C with the plain Pico SDK.
It can interact with the Volcano Hybrid, the Crafty and the Venty.
Everything can be controlled using on-screen menus on the device itself.
It even has a WiFi capable OTA bootloader to support wireless firmware upgrades.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/volcano_remote_c_dev.jpg"&gt;&lt;img alt="Volcano Remote C version in development" class="pic" src="https://www.xythobuz.de/img/volcano_remote_c_dev_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/volcano_remote_bootloader.jpg"&gt;&lt;img alt="Volcano Remote WiFi Bootloader" class="pic" src="https://www.xythobuz.de/img/volcano_remote_bootloader_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;To contain everything I designed a snug little 3D-printed case in OpenSCAD.
It contains the Pico W, a &lt;a href="https://shop.pimoroni.com/products/pico-lipo-shim?variant=32369543086163"&gt;Pico LiPo Shim&lt;/a&gt; and a &lt;a href="https://www.ebay.de/itm/255510046348?var=555462939784"&gt;"80mAh / 20 x 11 x 5mm / 501220" LiPo battery&lt;/a&gt;.
The Pi is mounted with four screws and the battery is held onto it with a drop of hot glue.
Then the LCD board just plugs on top to close it.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/volcano_remote_case_1.jpg"&gt;&lt;img alt="Volcano Remote case assembly" class="pic" src="https://www.xythobuz.de/img/volcano_remote_case_1_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/volcano_remote_case_2.jpg"&gt;&lt;img alt="Volcano Remote case opened" class="pic" src="https://www.xythobuz.de/img/volcano_remote_case_2_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/volcano_remote_case_3.jpg"&gt;&lt;img alt="Volcano Remote case power button" class="pic" src="https://www.xythobuz.de/img/volcano_remote_case_3_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;This is what the final result looks like.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/volcano_remote_case_4.jpg"&gt;&lt;img alt="Volcano Remote (final)" class="pic" src="https://www.xythobuz.de/img/volcano_remote_case_4_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;I'm quite happy with it, especially the software part.
It has many things I expect from nice modern embedded projects, and I've used a bunch of libraries to achieve these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/raspberrypi/pico-sdk"&gt;Pico SDK&lt;/a&gt; for working with the Pico&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/hathach/tinyusb"&gt;TinyUSB&lt;/a&gt; for the USB device implementation&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bluekitchen/btstack"&gt;BTstack&lt;/a&gt; for the Bluetooth connection&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/abbrev/fatfs"&gt;FatFS&lt;/a&gt; for the USB filesystem&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mcufont/mcufont"&gt;MCUFont&lt;/a&gt; for rendering text&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/hepingood/st7789"&gt;st7789&lt;/a&gt; for interacting with the LCD&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/usedbytes/picowota"&gt;picowota&lt;/a&gt; bootloader (&lt;a href="https://github.com/xythobuz/picowota"&gt;modified&lt;/a&gt; to work with the Flash config storage and LCD)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As usual you can find everything on &lt;a href="https://codeberg.org/xythobuz/Volcano-Remote"&gt;Codeberg&lt;/a&gt; and on &lt;a href="https://github.com/xythobuz/Volcano-Remote"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</description><author>xythobuz.de Blog</author><pubDate>Sat, 07 Dec 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">https://www.xythobuz.de/ble_remote.html</guid></item><item><title>Speaking at the .NET Conference Italia 2024</title><link>https://nicolaiarocci.com/speaking-at-the-dotnet-conference-italia-2024/</link><description>&lt;p&gt;I&amp;rsquo;m speaking at the &lt;a href="https://www.dotnetconference.it"&gt;.NET Conference Italia 2024&lt;/a&gt; on Dec 16th in Milan at the Microsoft House. My session is titled &lt;a href="https://www.dotnetconference.it/e/sessione/3589/C-13-e-NET-9-cosa-c%E2%80%99e-di-nuovo-e-interessante"&gt;C# 13 What&amp;rsquo;s New and Interesting&lt;/a&gt; and will be on the latest iteration of the C# language. We&amp;rsquo;ll also briefly touch on .NET 9, which was also just released. Hope to see you there (make sure to come to me to say Hi!)&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Sat, 07 Dec 2024 09:36:26 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/speaking-at-the-dotnet-conference-italia-2024/</guid></item><item><title>Site design standards</title><link>https://seirdy.one/meta/site-design/</link><description>The accessibility statement and design standards I hold myself to when creating seirdy.one</description><author>All content on Seirdy’s Home</author><pubDate>Sat, 07 Dec 2024 07:29:28 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/meta/site-design/</guid></item><item><title>Best practices for inclusive textual websites</title><link>https://seirdy.one/posts/2020/11/23/website-best-practices/</link><description>A lengthy guide to making simple, inclusive sites focused on content before form. Emphasizes brutalist design and accessibility to include under-represented users.</description><author>All content on Seirdy’s Home</author><pubDate>Sat, 07 Dec 2024 06:08:51 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/posts/2020/11/23/website-best-practices/</guid></item><item><title>Don't fall in love with your code</title><link>https://michaelsalim.co.uk/blog/dont-fall-in-love-with-your-code</link><description>Sometimes the best code is the code you delete. Don't love your code and do what's best for the codebase.</description><author>Michael Salim Blogs</author><pubDate>Sat, 07 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://michaelsalim.co.uk/blog/dont-fall-in-love-with-your-code</guid></item><item><title>Family Switch: A new and fun cast for a classic old story</title><link>https://olshansky.info/movie/family_switch/</link><description>Olshansky's review of Family Switch</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 07 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/movie/family_switch/</guid></item><item><title>My personal Bell Labs</title><link>https://ochagavia.nl/blog/my-personal-bell-labs/</link><description>Have you heard about Bell Labs? Recently, an article about it reached the Hacker News frontpage, triggering a flood of nostalgic comments. Bell Labs is, for many, a symbol of the ideal working environment for a curious mind: a place where there&amp;rsquo;s time, money and freedom to research whatever you want.
In my own case, having a curious mind drew me from a young age towards computers and lured me into the world of programming.</description><author>Consulting on Adolfo Ochagavía</author><pubDate>Sat, 07 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ochagavia.nl/blog/my-personal-bell-labs/</guid></item><item><title>Advent of Code 2024 Day 6: Janet Streams Using Fibers/Coroutines</title><link>https://fbrs.io/aoc2024-d6/</link><description>&lt;p&gt;Remember when it was all the rage to write articles about functional programming (FP) in Javascript by explaining how you can chain &lt;code&gt;.map&lt;/code&gt;, &lt;code&gt;.filter&lt;/code&gt; and &lt;code&gt;.reduce&lt;/code&gt; on lists? I hated that. I think that it conditions people into thinking that FP is all about list comprehensions. And when lists aren’t convenient anymore (more on this later), they then resort to imperative programming, thinking that FP isn’t suited to that kind of task.&lt;/p&gt;
&lt;p&gt;In Advent of Code (AoC) there are plenty of examples where eager list comprehensions won’t get you very far. An innocent looking &lt;code&gt;map().filter().map()&lt;/code&gt; can consume all your memory and make the garbage collector go crazy if you are creating and re-creating huge lists. A straight forward solution, as I mentioned earlier, is to fall back to more imperative patterns, such as the &lt;code&gt;loop&lt;/code&gt; macro in Janet.&lt;/p&gt;
&lt;p&gt;I was hoping that I could use Janet’s fibers/coroutines in combination with its stdlib &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;filter&lt;/code&gt; function to work on streams rather than eagerly evaluated lists. But that was not the case:&lt;/p&gt;
&lt;pre class="language-janet "&gt;&lt;code class="language-janet"&gt;(-&amp;gt;&amp;gt; (gen-stuff input)
     (map foo)
     (filter bar))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my testing on AoC day 6 (the first one where you walk around in a grid), the imperative version using &lt;code&gt;loop&lt;/code&gt; used about 80MB, the version that kicks things off with a generator and then runs this through various list comprehenions immediately took up around 10GB of memory. Here’s a concrete example:&lt;/p&gt;
&lt;pre class="language-janet "&gt;&lt;code class="language-janet"&gt;(defn gen-nums [] (coro (for i 0 10000000 (yield i)))

(comment
  # low mem usage
  (each v (gen-nums) (print v))

  # high mem usage
  (each v (map |(string&amp;#x2f;repeat (string $) 100) (gen-nums)) (print v)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But it turns out that it is surprisingly straight forward to have your cake and eat it too. You can use easily define streaming versions of &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;filter&lt;/code&gt; using the &lt;code&gt;coro&lt;/code&gt; function (or macro?):&lt;/p&gt;
&lt;pre class="language-janet "&gt;&lt;code class="language-janet"&gt;(defn map* [f ds] (coro (each v ds (yield (f v)))))

(defn filter* [f ds] (coro (each v ds (when (f v) (yield v)))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’ve used these for an upated implementation of day 6, which you can find &lt;a href="https://github.com/cideM/aoc2024-janet/blob/main/d6/main.janet#L52-L58"&gt;here&lt;/a&gt;&lt;/p&gt;</description><author/><pubDate>Sat, 07 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://fbrs.io/aoc2024-d6/</guid></item><item><title>Short story fika about being human and Historisk julmarknad at Gamla Uppsala</title><link>https://liza.io/short-story-fika-about-being-human-and-historisk-julmarknad-at-gamla-uppsala/</link><description>&lt;p&gt;It&amp;rsquo;s been another eventful week at the new place! I went back to work (the commute is even better than I had expected), hosted my next short story fika, met up with a friend from Stockholm, and went to a Christmas market in Gamla Uppsala.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Fri, 06 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/short-story-fika-about-being-human-and-historisk-julmarknad-at-gamla-uppsala/</guid></item><item><title>Kuma Fo by Les Amazones D'Afrique</title><link>https://nicolaiarocci.com/kuma-fo-by-les-amazones-dafrique/</link><description>&lt;blockquote&gt;
&lt;p&gt;Inspired by the historic Dahomey Amazons and founded by three of the biggest powerhouses in African music, Mamani Keïta, Mariam Doumbia, and Oumou Sangare, Les Amazones d&amp;rsquo;Afrique have been using their voices to advocate for women&amp;rsquo;s rights since their 2017 debut. The group has never shied away from mashing up tradition and technology. Still, on Musow Dance, with the endlessly inventive production of Jacknife Lee, they lean heavily into an almost entirely electronic sound, turning up the energy several notches with booming 808s, dramatic synth slides, and bursts of vintage disco. But none of these additions ever overshadow the true soul of the music, instead amplifying the already formidable voices of Les Amazones d&amp;rsquo;Afrique&amp;rsquo;s ever-evolving lineup (&lt;a href="https://daily.bandcamp.com/best-of-2024/the-best-albums-of-2024-g-m"&gt;source&lt;/a&gt;).&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Fri, 06 Dec 2024 17:52:41 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/kuma-fo-by-les-amazones-dafrique/</guid></item><item><title>I never want to sleep</title><link>https://callmeo.live/blog/i-never-want-to-sleep/</link><description>&lt;p&gt;There&amp;rsquo;s nothing better than a good night&amp;rsquo;s sleep. The problem: I don&amp;rsquo;t want to sleep, never been a fan of it.&lt;/p&gt;
&lt;p&gt;The average well-adjusted person&amp;rsquo;s day looks a little like this:
&lt;div class="centre"&gt;

  &lt;source type="image/webp" /&gt;
  &lt;source type="image/jpg" /&gt;
  &lt;img alt="" src="https://callmeo.live/resources/img/blog/2024/12/welladjusted.jpg" style="height: auto;" /&gt;

&lt;/div&gt;
&lt;/p&gt;
&lt;p&gt;My (not average, terribly-adjusted person) day however, looks more like this:
&lt;div class="centre"&gt;

  &lt;source type="image/webp" /&gt;
  &lt;source type="image/jpg" /&gt;
  &lt;img alt="" src="https://callmeo.live/resources/img/blog/2024/12/maladjusted.jpg" style="height: auto;" /&gt;

&lt;/div&gt;
&lt;/p&gt;
&lt;h3 id="perpetual-sleep-deprivation"&gt;Perpetual Sleep Deprivation&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Bedtime_procrastination"&gt;&lt;strong&gt;Revenge Bedtime Procrastination&lt;/strong&gt;&lt;/a&gt; is the name of the game. My day is unfulfilling, boring, spent doing a lot of things that I don&amp;rsquo;t really want to do (that&amp;rsquo;s life!) and I desperately claw back my time when the evening rolls around. As the world heads to sleep, the night is serene. Quiet, peaceful. After waiting an eternity I can finally pursue my interests free from interruption or judgement.&lt;/p&gt;
&lt;p&gt;&amp;hellip; And then I keep going. Midnight flies by. Anyone responsible would pack everything away and call it a night, but nah. &lt;strong&gt;Going to sleep means I&amp;rsquo;ll have to trudge through an entire day before I can get back to doing what I want&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Even if I know an early &amp;amp; full night&amp;rsquo;s sleep will restore me and allow me to perform at twice my best, immediate comfort trumps it. If I&amp;rsquo;m working on something, I won&amp;rsquo;t want to stop, so I&amp;rsquo;ll lock in to finish it regardless of when it&amp;rsquo;ll be done. If I&amp;rsquo;m relaxing, well there&amp;rsquo;s always &lt;em&gt;one more&lt;/em&gt; video, always another friend to say hello to, anything to put off the next day.&lt;/p&gt;
&lt;p&gt;Unfortunately my responsibilities aren&amp;rsquo;t as flexible as my sleep, and trying to deal with that conundrum is a nightmare in itself. I&amp;rsquo;ve spent the majority of my adult life a husk, dark circles permanently etched under my eyes, shambling from one day to another. On the good weeks I can scrape back my needed sleep over the weekend, but Monday is inevitable.&lt;/p&gt;
&lt;p&gt;Is it immature? Yep. Is it unhealthy? Absolutely. As if the brain fog and grogginess of perpetual sleep deprivation wasn&amp;rsquo;t bad enough I can assure you I&amp;rsquo;m walking into an early grave because of it, be it &lt;a href="https://www.theguardian.com/lifeandstyle/2011/feb/09/sleep-medical-research"&gt;stroke&lt;/a&gt; or &lt;a href="https://www.sleepfoundation.org/sleep-deprivation/how-sleep-deprivation-affects-your-heart"&gt;heart failure&lt;/a&gt;. The sad thing is that despite knowing this, I can&amp;rsquo;t bring myself to actually head to bed at a reasonable time and get enough sleep.&lt;/p&gt;
&lt;h3 id="the-causes"&gt;The causes&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Excitement and Motivation&lt;/strong&gt; - I&amp;rsquo;m not eager to trudge through the &lt;em&gt;boring stuff&lt;/em&gt; of the next day before I can do what I want&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ubiquity of the Internet&lt;/strong&gt; - 20 years ago the web was smaller, slower, and you&amp;rsquo;d only have your computer connected to it. The internet was an addition to our day-to-day lives, not a core part of it, and we could easily disconnect ourselves from it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bloody Americans&lt;/strong&gt; - &lt;del&gt;Unfortunately&lt;/del&gt; I know a lot of Americans. Because they live in a later timezone and have their own responsibilities, often the only time I can talk to them is in the evening&amp;hellip; And then I don&amp;rsquo;t want to stop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Nighttime is comfy&lt;/strong&gt; - As if the inherent thrill of being awake while the world sleeps isn&amp;rsquo;t good enough, add on the peace &amp;amp; quiet and the fact that you&amp;rsquo;re finally, truly alone&amp;hellip; It&amp;rsquo;s nice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;I&amp;rsquo;m not tired&lt;/strong&gt; - I spend my entire day feeling like death but when the sun goes down I could live forever. &lt;del&gt;Possible Vampirism?&lt;/del&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="possible-solutions-which-i-will-ignore"&gt;Possible solutions (which I will ignore)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;No more caffeine&lt;/strong&gt; - Moot point, I&amp;rsquo;ve been caffeine-free for 4 years now (or at least, only having decaf!). Across the board I feel better without it, and I can usually nod off in 15-30 minutes&amp;hellip; The issue is that I don&amp;rsquo;t want to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoid Blue Light&lt;/strong&gt; - I kinda try? I have all my devices set to the lowest brightness with warm Night-light settings practically set to max but&amp;hellip; It&amp;rsquo;s more that having said devices means I don&amp;rsquo;t want to disconnect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Disconnect&lt;/strong&gt; - I mean I do so before 1am, but &lt;em&gt;okay&lt;/em&gt;. The issue with programs that shut your devices off after a set time is that I could just switch devices or turn things back on.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Grow up, be responsible&lt;/strong&gt; - I realise that this is the answer, but &lt;em&gt;eughhhhhhh&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do more&lt;/strong&gt; - Fair. The more I do during the day, the more tired I am when the evening rolls around.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Get something to live for&lt;/strong&gt; - This one is probably too deep for this blog post. Maybe I&amp;rsquo;d actually sleep if I was with someone who&amp;rsquo;d make the boring stuff in the day worth it&amp;hellip; But knowing me I&amp;rsquo;d probably attract a fellow internet vampire and it wouldn&amp;rsquo;t fix anything.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don&amp;rsquo;t really have much else to say. The act of sleeping itself is lovely, and getting a healthy amount of it feels great but I keep denying myself it. Occasionally I&amp;rsquo;ll have a week or two where I sleep on time and get enough of it, but it never lasts and&amp;hellip; I don&amp;rsquo;t know.&lt;/p&gt;
&lt;p&gt;If the day had an extra ~4 hours so I could extra time to myself AND get a full night&amp;rsquo;s sleep&amp;hellip; Honestly I&amp;rsquo;d waste it by still choosing not to sleep. Oops.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;Edit 2024/12/30:&lt;/strong&gt; For the last two weeks I&amp;rsquo;ve somehow found myself feeling tired when the evening rolls around. I&amp;rsquo;ve been able (and willing!) to switch my brain off to sleep, and on the days where I haven&amp;rsquo;t I&amp;rsquo;ve ended up conking out before it got too late.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t have a concrete reason why, only a suggestion. I know that Winter can take a toll on my mood to the point where things are less engaging. Without me wanting to lose myself in something, the less chance I have at staying up. Also nothing beats curling up in warm cozy bed during the cold winter nights.&lt;/p&gt;
&lt;p&gt;Hopefully I can manage to keep this up through the new year and beyond. Things feel so much better when you actually get sleep.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;What pushed me to finish this blog post was a power cut a few days back. Turns out that disconnecting &lt;em&gt;does&lt;/em&gt; work, just not if you have the ability to reconnect.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>callmeolive</author><pubDate>Fri, 06 Dec 2024 16:50:00 GMT</pubDate><guid isPermaLink="true">https://callmeo.live/blog/i-never-want-to-sleep/</guid></item><item><title>Why Askama?</title><link>https://mattrighetti.com/2024/12/06/why-askama.html</link><description>Askama implements a template rendering engine based on Jinja. It generates Rust code from your templates at compile time based on a user-defined struct to hold the template&amp;#8217;s context.</description><author>mattrighetti</author><pubDate>Fri, 06 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://mattrighetti.com/2024/12/06/why-askama.html</guid></item><item><title>AI Code Assistants: Blurring the Lines Between Categories</title><link>https://prashamhtrivedi.in/post/code_assistants_blurring_line/</link><description>For long, AI Coding tools have naturally split into specialized roles to solve different developer needs, Inline assistants for real-time suggestions and Multi-file editors for broader changes. Today, these approaches are converging, making our development workflows more efficient than ever.
I see two distinct categories of AI code assistants in our development landscape.
The inline assistance category, championed by GitHub Copilot and Codeium, excels at real-time completions and suggestions. These assistants are like pair programmers who peek over your shoulder, helping you craft code line by line.</description><author>Prasham H Trivedi</author><pubDate>Fri, 06 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/post/code_assistants_blurring_line/</guid></item><item><title>There Is No Universal De-Select-All Idiom</title><link>https://www.da.vidbuchanan.co.uk/blog/deselect-all.html</link><author>David Buchanan's Blog</author><pubDate>Fri, 06 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.da.vidbuchanan.co.uk/blog/deselect-all.html</guid></item><item><title>Should you be worried about H5N1?</title><link>https://www.georgeyw.com/should-you-be-worried-about-h5n1/</link><description>Ca-caw!</description><author>This Is My True Name</author><pubDate>Thu, 05 Dec 2024 23:25:23 GMT</pubDate><guid isPermaLink="true">https://www.georgeyw.com/should-you-be-worried-about-h5n1/</guid></item><item><title>Non-Identity Problem and robust climate action - practical philosophy seminar notes</title><link>https://liza.io/non-identity-problem-and-robust-climate-action-practical-philosophy-seminar-notes/</link><description>&lt;p&gt;Last week I dropped in on what turned out to be a seminar in practical philosophy, featuring a paper about &lt;a href="../crashed-a-talk-on-moral-luck-and-fairness-today-it-was-great/"&gt;resultant moral luck&lt;/a&gt;. This week I was back, with more preparation this time - i.e., I read the paper in advance.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Thu, 05 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/non-identity-problem-and-robust-climate-action-practical-philosophy-seminar-notes/</guid></item><item><title>Note 153</title><link>https://qubyte.codes/notes/1733403283960</link><description>&lt;p&gt;Back in Fahrenheit Coffee in Toronto.&lt;/p&gt;

    &lt;img alt="Black coffee in a white cup and saucer on a wooden bar, facing a window onto the street." src="https://qubyte.codes/images/1733403206602.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Thu, 05 Dec 2024 14:54:43 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1733403283960</guid></item><item><title>Modern art = Indie gaming</title><link>https://robkohr.com/articles/modern-art---indie-gaming</link><description>Modern art = Indie gaming</description><author>RobKohr's Blog</author><pubDate>Thu, 05 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/modern-art---indie-gaming</guid></item><item><title>LLM chat navigation</title><link>https://xenodium.com/llm-chat-navigation</link><description>&lt;p&gt;LLM chats are often handy for refining answers to a question or task, part of a bigger goal. Navigating the chat transcript, copying and pasting, can be a frequent operation in the bigger goal. If we can do it more efficiently, the better.&lt;/p&gt;
&lt;p&gt;While &lt;a href="https://github.com/xenodium/chatgpt-shell/issues"&gt;chatgpt-shell&lt;/a&gt; offered &lt;code&gt;chatgpt-shell-next-item&lt;/code&gt; and &lt;code&gt;chatgpt-shell-previous-item&lt;/code&gt; commands, it had a few rough edges which prevented me from fully adopting. The default bindings &lt;code&gt;C-c C-n&lt;/code&gt; and &lt;code&gt;C-c C-p&lt;/code&gt; didn't exactly help either, making repeated navigation fairly clunky. &lt;a href="https://karthinks.com/software/it-bears-repeating/"&gt;repeat-mode&lt;/a&gt; would have helped a little, yet I was yearning for a familiar experience… more like &lt;code&gt;tab&lt;/code&gt; and &lt;code&gt;Shift-tab&lt;/code&gt; in web browsers.&lt;/p&gt;
&lt;p&gt;While shells often &lt;code&gt;tab&lt;/code&gt;-complete commands and/or arguments, I'm not super convinced &lt;code&gt;tab&lt;/code&gt; completion is a good candidate for an LLM Emacs shell. Having said that, searching for previous prompts a la &lt;code&gt;Ctr-r&lt;/code&gt; is indeed a handy feature and already supported in &lt;a href="https://github.com/xenodium/chatgpt-shell/issues"&gt;chatgpt-shell&lt;/a&gt; (via &lt;code&gt;M-r&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This is all to say that the with the latest &lt;code&gt;chatgpt-shell&lt;/code&gt; navigation changes (using &lt;code&gt;&amp;lt;tab&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;backtab&amp;gt;&lt;/code&gt; bindings), the &lt;code&gt;chatgpt-shell&lt;/code&gt; experience feels way more natural. You can see it in action…&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/llm-chat-navigation/shell.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Tab navigation jumps between prompts, links, and code blocks. You may have noticed code blocks are automatically selected, in case you want to quickly copy and paste elsewhere.&lt;/p&gt;
&lt;p&gt;This mode of navigation is also present in the &lt;a href="https://lmno.lol/alvaro/chatgpt-shell-goes-multi-model#a-shell-hybrid"&gt;compose UX&lt;/a&gt; (via M-x &lt;code&gt;chatgpt-shell-prompt-compose&lt;/code&gt;). In addition to &lt;code&gt;&amp;lt;tab&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;backtab&amp;gt;&lt;/code&gt;, you can use &lt;code&gt;n&lt;/code&gt; and &lt;code&gt;p&lt;/code&gt; bindings (post prompt submission).&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/llm-chat-navigation/compose-tab.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Give the new navigation a try. See how it feels. Some of it is fairly fresh, so &lt;a href="https://github.com/xenodium/chatgpt-shell/issues/new"&gt;please file bugs if needed&lt;/a&gt;.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Thu, 05 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/llm-chat-navigation</guid></item><item><title>Malware Insights: GitHub Actions Script Injection</title><link>https://cookie.engineer/weblog/articles/malware-insights-github-actions-script-injection.html</link><description>How to infiltrate CI/CD runners because they don't sanitize arbitrary string inputs.</description><author>Cookie Engineer's Web Log</author><pubDate>Thu, 05 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://cookie.engineer/weblog/articles/malware-insights-github-actions-script-injection.html</guid></item><item><title>Axum, Askama, HTMX</title><link>https://mattrighetti.com/2024/12/05/axum-askama-htmx.html</link><description>Lately I&amp;#8217;ve been working on Ulry, after tiressly trying to make Next.js work for me, I realised it wasn&amp;#8217;t the right fit (I&amp;#8217;m not alone). I decided to make a U-turn and go all in with Rust and SSR.</description><author>mattrighetti</author><pubDate>Thu, 05 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://mattrighetti.com/2024/12/05/axum-askama-htmx.html</guid></item><item><title>Learnings</title><link>https://rajeevnaruka.com/blog/learnings</link><description>...</description><author>Rajeev Singh Naruka</author><pubDate>Thu, 05 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://rajeevnaruka.com/blog/learnings</guid></item><item><title>Your Next Two Zeroes</title><link>https://taylor.town/next-two-zeroes</link><description>Exceed yourself; become a novice again, again and again.</description><author>taylor.town</author><pubDate>Thu, 05 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/next-two-zeroes</guid></item><item><title>Paper: The Unreasonable Effectiveness of Monte Carlo Simulations in A/B Testing</title><link>https://bytepawn.com/paper-unreasonable-effectiveness-monte-carlo-ab-testing.html</link><description>&lt;p&gt;I run Monte Carlo simulations of content production over random Watts-Strogatz graphs to show various effects relevant to modeling and understanding Randomized Controlled Trials on social networks: the network effect, spillover effect, experiment dampening effect, intrinsic dampening effect, clustering effect, degree distribution effect and the experiment size effect. I will also define some simple metrics to measure their strength. When running experiments these potentially unexpected effects must be understood and controlled for in some manner, such as modeling the underlying graph structure to establish a baseline. &lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/paper-monte-carlo-experiments-of-network-effects-in-randomized-controlled-trials.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Thu, 05 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/paper-unreasonable-effectiveness-monte-carlo-ab-testing.html</guid></item><item><title>Bringing K/V Context Quantisation to Ollama</title><link>https://smcleod.net/2024/12/bringing-k/v-context-quantisation-to-ollama/</link><description>K/V context cache quantisation has been added to Ollama. This enables significant reductions in VRAM usage, allowing users to realise the potential of expanded context sizes and run larger models at their existing context sizes.</description><author>smcleod.net</author><pubDate>Wed, 04 Dec 2024 22:00:02 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/12/bringing-k/v-context-quantisation-to-ollama/</guid></item><item><title>Five Advanced Meditation Techniques (also, drugs)</title><link>https://superbowl.substack.com/p/five-advanced-meditation-techniques</link><description>A few ways to turn up the volume if you're feeling stuck</description><author>Superb Owl</author><pubDate>Wed, 04 Dec 2024 21:00:42 GMT</pubDate><guid isPermaLink="true">https://superbowl.substack.com/p/five-advanced-meditation-techniques</guid></item><item><title>20 Years of Blogging and I’m still a n00b</title><link>https://caseysoftware.com/blog/20-years-of-blogging-and-im-still-a-n00b?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=20-years-of-blogging-and-im-still-a-n00b</link><description>&lt;p&gt;As of today, I&amp;#8217;ve been blogging for 20 years. Holy crap, that&amp;#8217;s a long time. I remember when I started,&amp;#8230;&lt;/p&gt;
The post &lt;a href="https://caseysoftware.com/blog/20-years-of-blogging-and-im-still-a-n00b"&gt;20 Years of Blogging and I’m still a n00b&lt;/a&gt; appeared first on &lt;a href="https://caseysoftware.com"&gt;Caseysoftware&lt;/a&gt;.</description><author>Caseysoftware</author><pubDate>Wed, 04 Dec 2024 15:16:00 GMT</pubDate><guid isPermaLink="true">https://caseysoftware.com/blog/20-years-of-blogging-and-im-still-a-n00b?utm_source=rss&amp;utm_medium=rss&amp;utm_campaign=20-years-of-blogging-and-im-still-a-n00b</guid></item><item><title>Breakage! in the Cargo.toml — How Rust Package Features Work (And Break)</title><link>https://predr.ag/blog/breakage-in-the-cargo-toml-how-rust-package-features-work/</link><description>&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/obi1kenobi/cargo-semver-checks/releases/tag/v0.37.0" rel="external"&gt;&lt;code&gt;cargo-semver-checks&lt;/code&gt; v0.37&lt;/a&gt; can now scan &lt;code&gt;Cargo.toml&lt;/code&gt; files for breakage! In this post: a primer on Rust package features, and how innocuous-looking &lt;code&gt;Cargo.toml&lt;/code&gt; changes can break your users.&lt;/em&gt;&lt;/p&gt;</description><author>Predrag Gruevski's blog and personal site.</author><pubDate>Wed, 04 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://predr.ag/blog/breakage-in-the-cargo-toml-how-rust-package-features-work/</guid></item><item><title>Awesome elisp</title><link>https://xenodium.com/awesome-elisp</link><description>&lt;p&gt;A few days ago, redditor &lt;a href="https://www.reddit.com/user/gollyned"&gt;gollyned&lt;/a&gt; asked about &lt;a href="https://www.reddit.com/r/emacs/comments/1h4eos1/best_practices_developing_on_top_of_modern_elisp/"&gt;best practices: developing on top of modern elisp packages&lt;/a&gt;. It reminded of my &lt;a href="https://lmno.lol/alvaro/modern-elisp-libraries"&gt;modern Emacs lisp libraries&lt;/a&gt; post, which I shared with them.&lt;/p&gt;
&lt;p&gt;While my post is roughly 4.5 years old, these days I continue to reach out to the likes of &lt;a href="https://nicolas.petton.fr/ressources/emacsconf2015/"&gt;seq.el&lt;/a&gt;, &lt;a href="https://nicolas.petton.fr/ressources/emacsconf2015/"&gt;map.el&lt;/a&gt;, &lt;a href="https://github.com/emacs-mirror/emacs/blob/master/lisp/emacs-lisp/subr-x.el"&gt;subr-x.el&lt;/a&gt; and &lt;a href="https://endlessparentheses.com/new-on-elpa-and-in-emacs-25-1-let-alist.html"&gt;let-alist.el&lt;/a&gt; on a regular basis. My post also &lt;a href="https://lmno.lol/alvaro/modern-elisp-libraries"&gt;shared some great third party options&lt;/a&gt;. Maybe my post could use an update? Happy to take suggestions.&lt;/p&gt;
&lt;p&gt;A few days later, I ran into &lt;a href="https://github.com/p3r7/awesome-elisp"&gt;awesome-elisp&lt;/a&gt;, which aggregates a ton of resources to check out. Funnily enough, &lt;a href="https://lmno.lol/alvaro/emacs-lisp-bookmarks"&gt;I had bookmarked it a long while ago&lt;/a&gt; and simply forgot about it. On a somewhat related note, when I reviewed my old list of bookmarks, I didn't have Yoo Box's &lt;a href="https://yoo2080.wordpress.com/2014/07/04/it-is-not-hard-to-read-lisp-code/"&gt;it is not hard to read Lisp code&lt;/a&gt; bookmarked, which changed the way I viewed and read elisp code (spoiler alert, as a tree). I remember how well this approach also translated to languages like Objective-C, enabling me to inline more things without worrying too much.&lt;/p&gt;
&lt;p&gt;In any case, the &lt;a href="https://www.reddit.com/r/emacs/comments/1h4eos1/best_practices_developing_on_top_of_modern_elisp/"&gt;reddit post&lt;/a&gt; was another reminder for me to go and check out some of those bookmarks I never got to read, including the &lt;a href="https://github.com/p3r7/awesome-elisp"&gt;awesome-elisp&lt;/a&gt; one.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Wed, 04 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/awesome-elisp</guid></item><item><title>Build a search engine, not a vector DB</title><link>https://saeedesmaili.com/notes/build-a-search-engine-not-a-vector-db/</link><description>&lt;blockquote&gt;
&lt;p&gt;If you want to make a good RAG tool that uses your documentation, you should start by making a search engine over those documents that would be good enough for a human to use themselves.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;This is exactly what I&amp;rsquo;ve been trying to communicate in my org in the past few months. It&amp;rsquo;s 2024 and we still can&amp;rsquo;t have a proper search engine in organizations to find relevant information from various sources. While this problem remains to be solved, organizations are adapting RAG and AI into their tooling, but are missing the important R of the RAG: Retrieval. I&amp;rsquo;ve been an advocate of prioritizing search engines over any AI related tool in the past few months, and I found it refreshing to read about this somewhere else:&lt;/p&gt;</description><author>Saeed Esmaili</author><pubDate>Wed, 04 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://saeedesmaili.com/notes/build-a-search-engine-not-a-vector-db/</guid></item><item><title>I hereby pardon all parking enforcement officers</title><link>https://taylor.town/pardon-2024</link><description>Build mass-transit. Reduce parking wars. Promote non-violent parking/traffic enforcement.</description><author>taylor.town</author><pubDate>Wed, 04 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/pardon-2024</guid></item><item><title>Is humanity a promise yet to be fulfilled? Becoming human at Uppsalas Stadsbiblioteket</title><link>https://liza.io/is-humanity-a-promise-yet-to-be-fulfilled-becoming-human-at-uppsalas-stadsbiblioteket/</link><description>&lt;p&gt;Tonight I went to a live podcast recording of the &lt;a href="https://forestofthought.com/"&gt;&lt;em&gt;Forest of Thought&lt;/em&gt;&lt;/a&gt; podcast at Uppsalas Stadsbiblioteket. The theme was: &lt;em&gt;Becoming human - the search for soul in turbulent times&lt;/em&gt;.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Tue, 03 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/is-humanity-a-promise-yet-to-be-fulfilled-becoming-human-at-uppsalas-stadsbiblioteket/</guid></item><item><title>go yolo</title><link>https://evilcookie.de/go-yolo.html</link><description/><author>blog</author><pubDate>Tue, 03 Dec 2024 21:55:26 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/go-yolo.html</guid></item><item><title>Can Rust teach me computers?</title><link>https://blog.vslira.net/2024/12/can-rust-teach-me-computers.html</link><description>&lt;p style="text-align: justify;"&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;One of the books on my reading list is Bryant and O'Hallaron's &lt;a href="https://teachyourselfcs.com/#architecture" style="font-style: italic;"&gt;Computer Systems: A Programmer's Perspective&lt;/a&gt;. As the name implies, it teaches you how computers work. The book uses C since it's a low-level language that "exposes" the machine and it appeals to me because I've been eager to learn both systems-level programming and computer hardware, and this book addresses both areas using C.&lt;/p&gt;&lt;p style="text-align: justify;"&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; Now, the reader might not be aware, but there's currently a low-level language holy war between Rust and C going on (well, more like a two-front war with Rust tackling C and C++ at the same time). Which is fine and all - I've got no dog in this fight - but it got me thinking: Is there some book or piece of content that can teach me the same breadth of hardware knowledge based on Rust? Even though learning is fun, I'd rather learn a language at the same time I learn about hardware.&lt;/span&gt;&lt;/p&gt;&lt;p style="text-align: justify;"&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;I don't expect to become a systems programmer any time soon, but I admit that writing, say, high-performance libraries to be called from Python sounds appealing, and Rust seems to be gaining a lot of traction on that front. Maybe I can't escape learning C for the conceptual low-level stuff and then Rust to leverage the new ecosystem, but that's more trouble than I was expecting. I would appreciate any thoughts on how to deal with this question.&lt;/span&gt;&lt;/p&gt;</description><author>vslira's blog</author><pubDate>Tue, 03 Dec 2024 21:24:42 GMT</pubDate><guid isPermaLink="true">https://blog.vslira.net/2024/12/can-rust-teach-me-computers.html</guid></item><item><title>Karma ripples</title><link>https://www.aswathkrishnan.com/2024/11/karma-ripples.html</link><description>&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEiBjLNg5HoEGW-lkVYTZri1PkyVHjW9uBDS_8Ui4J2uNbWla9Sy_GD-bSqMpzNEyL2O0D7wvf0x7e2OkRHzEZdAgEtG8GI-fCY8kUlLNUUgNPyTTEJJuylSufjcSyvnpP8Nexs3N61J3PWhIcDNLWDJ9H3pmLqWzErkX6JUhvegys3NwEqAPeGky_b3N4ga" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="130" src="https://blogger.googleusercontent.com/img/a/AVvXsEiBjLNg5HoEGW-lkVYTZri1PkyVHjW9uBDS_8Ui4J2uNbWla9Sy_GD-bSqMpzNEyL2O0D7wvf0x7e2OkRHzEZdAgEtG8GI-fCY8kUlLNUUgNPyTTEJJuylSufjcSyvnpP8Nexs3N61J3PWhIcDNLWDJ9H3pmLqWzErkX6JUhvegys3NwEqAPeGky_b3N4ga=w195-h130" width="195" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;When you’re unkind to someone, you’re not just creating a fleeting moment of discomfort; you set off waves—within you, within them, and outward into the collective consciousness of those around you. Your behavior leaves an emotional residue and becomes part of your and someone else’s story and neural wiring. They carry that hurt forward—sometimes consciously, sometimes not—spreading it like an emotional contagion to others in their orbit. What starts as a harsh word can grow into a pattern, a culture, or even a generational legacy.&lt;p&gt;&lt;/p&gt;&lt;p&gt;Kindness, on the other hand, is a ripple effect we all wish to swim in. Every small act of generosity or love creates its own chain reaction. Neuroscience shows that acts of kindness release oxytocin and dopamine, not just for the giver and the receiver but for those who witness it too. These interactions shift our neural wiring toward empathy and openness. They teach us—subconsciously—that the world is safer, better, and more collaborative than it might sometimes seem.&lt;/p&gt;&lt;p&gt;Karma, often misunderstood as some cosmic ledger balanced in the afterlife, is far more immediate and scientific. Karma is simply cause and effect. Every thought, word, and action shapes the trajectory of your life and the world around you in real-time. Think of it less like a divine judgment and more like a behavioral algorithm. Each action you take feeds into the feedback loop of your psyche, reinforcing patterns that dictate who you are becoming and how you affect the lives of others.&lt;/p&gt;&lt;div&gt;&lt;p&gt;The world today—its anxieties, conflicts, love, and beauty—is the sum total of these ripples (&lt;a href="https://www.aswathkrishnan.com/2022/08/the-theory-of-domino-destiny.html" target="_blank"&gt;Theory of Domino Destiny&lt;/a&gt;). It’s a mosaic of decisions, many made unconsciously, accumulating into today’s reality. Every moment is an opportunity to choose the ripples you create. You can even be a Karma alchemist, and absorb, transform, or stop negative ripples from others. Your smallest choices—whether to snap at someone, to show them grace, or stand up for what's right—aren’t just about you. They shape the larger web of human experience.&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;Samsara, another Hindu and Buddhist concept, isn’t just about escaping some mystical cycle of reincarnation. It’s about transcending the daily cycles of suffering we perpetuate through thoughtless actions and negative Karma. Liberation is not found in some distant metaphysical plane—it’s found here, now, in the choice to stop adding pain to the world and to start choosing compassion, connection, and kindness instead. Karma isn’t waiting for you. It’s already in motion, responding to your every move.&amp;nbsp;&lt;/p&gt;&lt;/div&gt;&lt;div&gt;&lt;p&gt;The question isn’t whether Karma is happening—it’s whether you’re conscious of the Karma you create. Are you a generator of chaos, or a cultivator of harmony? What ripples are you sending out and within? Because the truth is, those ripples don’t just change the world—they&amp;nbsp;&lt;em&gt;are&lt;/em&gt;&amp;nbsp;the world and they are your life experience. Every moment, you have the chance to shape it.&amp;nbsp;&lt;/p&gt;&lt;/div&gt;</description><author>Aswath Krishnan</author><pubDate>Tue, 03 Dec 2024 17:54:48 GMT</pubDate><guid isPermaLink="true">https://www.aswathkrishnan.com/2024/11/karma-ripples.html</guid></item><item><title>Modules and imports in mgmt</title><link>https://purpleidea.com/blog/2024/12/03/modules-and-imports-in-mgmt/</link><description>&lt;p&gt;&lt;a href="https://github.com/purpleidea/mgmt/"&gt;Mgmt&lt;/a&gt; modules are used for structuring
your code across multiple files. This keeps things readable and allows for code
reusability. For example, it should be easy to write a single module which can
be effectively used by many different parties. To me, it never felt like code
reuse was a core design objective with other tools. In mgmt it has been since
the very beginning. I&amp;rsquo;m very proud of our module system, so let me introduce you
to it properly.&lt;/p&gt;</description><author>The Technical Blog of James on purpleidea.com</author><pubDate>Tue, 03 Dec 2024 09:56:37 GMT</pubDate><guid isPermaLink="true">https://purpleidea.com/blog/2024/12/03/modules-and-imports-in-mgmt/</guid></item><item><title>Happy perfect square year 45^2 = 2025</title><link>https://robkohr.com/articles/happy-perfect-square-year-45-2---2025</link><description>Happy perfect square year 45^2 = 2025</description><author>RobKohr's Blog</author><pubDate>Tue, 03 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/happy-perfect-square-year-45-2---2025</guid></item><item><title>Hammered by attempted hackers</title><link>https://robkohr.com/articles/hammered-by-attempted-hackers</link><description>Hammered by attempted hackers</description><author>RobKohr's Blog</author><pubDate>Tue, 03 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/hammered-by-attempted-hackers</guid></item><item><title>Resizing AWS Mac Volumes</title><link>https://dylanpaulus.com/posts/2024/resizing-mac-aws-volumes/</link><description>&lt;p&gt;I recently ran into an issue with our Mac build machine in AWS, where the EC2 instance kept running out of disk space. It should be easy enough to bump the EBS volume size and call it a day. Wrong. No matter what I changed the volume size to, the Mac instance would not recognize the new space. What gives?&lt;br /&gt;
After some digging, I found that Mac requires us to resize the OS APFS (Apple File System) container after providing the volume with more space. Let’s walk through, from the start, how to increase the size of an EBS volume on a Mac AWS instance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Increase EBS Volume Size&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In the EC2, under Instances, select the instance that needs the volume resized.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="./ec2-dashboard.png" /&gt;&lt;/p&gt;
&lt;p&gt;Once inside the instance, select &lt;em&gt;Storage&lt;/em&gt; and the volume that needs to be resized.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="./ec2-storage-section.png" /&gt;&lt;/p&gt;
&lt;p&gt;Then, with the volume selected, choose &lt;em&gt;Modify Volume&lt;/em&gt; from the &lt;em&gt;Actions&lt;/em&gt; dropdown.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="./modify-volume-dash.png" /&gt;&lt;/p&gt;
&lt;p&gt;Update &lt;em&gt;Size&lt;/em&gt; to be the new desired size of the volume.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="./modify-volume.png" /&gt;&lt;/p&gt;
&lt;p&gt;After the volume has been resized, we need to reboot the EC2 instance. Navigate back to the instance dashboard and select &lt;em&gt;Instance State&lt;/em&gt; &amp;gt; &lt;em&gt;Reboot instance&lt;/em&gt;.&lt;br /&gt;
This may take some time for the instance to fully reboot.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="./reboot.png" /&gt;&lt;/p&gt;
&lt;p&gt;But once the instance is back up and in a healthy state, connect into the instance by going into the instance dashboard and selecting &lt;em&gt;Connect&lt;/em&gt;.&lt;br /&gt;
Use whichever method you prefer to connect to the instance.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="./connect.png" /&gt;&lt;/p&gt;
&lt;p&gt;Now we can see how much disk space this instance has. To do this run &lt;code&gt;df -h&lt;/code&gt; or &lt;em&gt;diskutil&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;diskutil list external physical
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="Output running " src="./show-space.png" /&gt;&lt;/p&gt;
&lt;p&gt;At this point you will notice that even though the volume has been increased, the OS has not recognized the new space. &lt;code&gt;diskutil&lt;/code&gt; and &lt;code&gt;df&lt;/code&gt; is showing the old volume size.&lt;br /&gt;
The Apple file system doesn't expand automatically to fill the new space to avoid potential data loss, and provide users control over the space on the system.&lt;/p&gt;
&lt;p&gt;To expand APFS use the new space run the following commands:&lt;/p&gt;
&lt;p&gt;First, repair the disk to fix and prevent any potential problems:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PDISK=$(diskutil list physical external | head -n 1 | cut -d " " -f 1)
yes | sudo diskutil repairDisk $PDISK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, resize the APFS container to take advantage of the new space:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;APFSCONT=$(diskutil list physical external | grep "Apple_APFS" | tr -s " " | cut -d " " -f 8)
sudo diskutil apfs resizeContainer $APFSCONT 0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running the above commands, you will see the new space reflected by again running &lt;code&gt;df -h&lt;/code&gt; or &lt;code&gt;diskutil list external physical&lt;/code&gt;!&lt;/p&gt;</description><author>Dylan Paulus' Blog</author><pubDate>Tue, 03 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://dylanpaulus.com/posts/2024/resizing-mac-aws-volumes/</guid></item><item><title>Data Loading - Comparing Common Tooling</title><link>https://blog.londogard.com/posts/2024-12-03-data-loading-comparison/</link><description>&lt;p&gt;This blog was supposed to be more in-depth but my enthusiasm was drastically cut and I felt like splitting it up into multiple smaller one, whereas &lt;a href="../../posts/2024-10-24-data-loading-daft/"&gt;daft one&lt;/a&gt; is already uploaded.&lt;/p&gt;
&lt;blockquote class="blockquote"&gt;
&lt;p&gt;I started writing a “recipe-book” for &lt;code&gt;daft&lt;/code&gt; where I realized it wasn’t as smoothly integrated as a lot of other tools. I believe that the &lt;code&gt;DataFrame&lt;/code&gt; format is both a winning and loosing concept, it’s very helpful but when you need to use two columns the way &lt;code&gt;Ray&lt;/code&gt;, &lt;code&gt;HuggingFace Datasets&lt;/code&gt; and others map data using &lt;code&gt;dict&lt;/code&gt; is a winning concept for both &lt;em&gt;element by element&lt;/em&gt; and &lt;em&gt;batch&lt;/em&gt; mapping. With a &lt;code&gt;dict&lt;/code&gt; way to map &lt;code&gt;DataFrame&lt;/code&gt; I think that &lt;code&gt;daft&lt;/code&gt; might end up the perfect tool.&lt;/p&gt;
&lt;p&gt;For now I believe Daft is better utilized as an ETL framework, but in the near future it might become great for ML too.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Anyhow, today I’ll compare the developer experience and performance of different tools for data loading.&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;&lt;a href="https://huggingface.co/docs/datasets/index"&gt;HuggingFace Datasets&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.ray.io/en/latest/data/data.html"&gt;Ray Data&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://getdaft.io/"&gt;Daft&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pytorch.org/"&gt;“PyTorch Native” (Dataset &amp;amp; DataLoader)&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;All of the chosen tools are quite awesome, but HuggingFace and Ray can export to TensorFlow additionally. Although Ray currently cannot handle &lt;code&gt;RaggedTensor&lt;/code&gt; which is required for models with variable output - a letdown!&lt;/p&gt;
&lt;section class="level2" id="quick-introduction"&gt;
&lt;h2 class="anchored"&gt;Quick Introduction&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Hugging Face Datasets&lt;/strong&gt; offers easy access to a vast library of datasets, with efficient memory handling through streaming and memory-mapping. Its API simplifies data loading and transformation for direct use with PyTorch and TensorFlow.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ray Data&lt;/strong&gt; enables scalable, distributed data processing across multiple nodes, ideal for large datasets. It integrates with Ray’s ML tools for parallel training and distributed transformations. It’s the tool for Large Language Model training, even embraced by OpenAI in their ChatGPT &lt;a href="https://thenewstack.io/how-ray-a-distributed-ai-framework-helps-power-chatgpt/"&gt;source&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Daft&lt;/strong&gt; is a high-performance data processing library with lazy evaluation, optimized for structured data formats like Parquet and Arrow. It’s a strong choice for single-node and multi-node data preparation with PyTorch compatibility. It utilizes Ray to achieve multi-node behavior.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;PyTorch’s Dataset and DataLoader&lt;/strong&gt; offer a simple and flexible way to load data with minimal memory overhead, ideal for in-memory and custom datasets. It’s lightweight but lacks distributed and lazy loading features.&lt;/p&gt;
&lt;table class="caption-top table"&gt;
&lt;caption&gt;Table Summarization&lt;/caption&gt;
&lt;colgroup&gt;
&lt;col style="width: 28%;" /&gt;
&lt;col style="width: 24%;" /&gt;
&lt;col style="width: 9%;" /&gt;
&lt;col style="width: 4%;" /&gt;
&lt;col style="width: 32%;" /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr class="header"&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Hugging Face Datasets&lt;/th&gt;
&lt;th&gt;Ray Data&lt;/th&gt;
&lt;th&gt;Daft&lt;/th&gt;
&lt;th&gt;PyTorch Dataset + DataLoader&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;Parallel Processing&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;++&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;Distributed Processing&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;Caching &amp;amp; Memory Mapping&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;Lazy Loading&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;++&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;+++ (depends)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;Simple to Use&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;++&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;Built-in Dataset Access&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;Custom Transformations&lt;/td&gt;
&lt;td&gt;++&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;ML Framework Support&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;+++&lt;/td&gt;
&lt;td&gt;++&lt;/td&gt;
&lt;td&gt;++&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/section&gt;
&lt;section class="level2" id="mini-benchmark"&gt;
&lt;h2 class="anchored"&gt;Mini Benchmark&lt;/h2&gt;
&lt;table class="caption-top table"&gt;
&lt;colgroup&gt;
&lt;col style="width: 32%;" /&gt;
&lt;col style="width: 13%;" /&gt;
&lt;col style="width: 13%;" /&gt;
&lt;col style="width: 6%;" /&gt;
&lt;col style="width: 20%;" /&gt;
&lt;col style="width: 12%;" /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr class="header"&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Num_worker&lt;/th&gt;
&lt;th&gt;Pin_memory&lt;/th&gt;
&lt;th&gt;Cache&lt;/th&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;strong&gt;HF Element&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;.map&lt;/td&gt;
&lt;td&gt;6m48s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;td&gt;.with_transform&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3m23s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;strong&gt;HF Batched&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;False&lt;/td&gt;
&lt;td&gt;.map&lt;/td&gt;
&lt;td&gt;7m14s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;td&gt;.map&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3m22s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;strong&gt;Torch Dataset/Loader&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3m20s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;strong&gt;Daft&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;daft-default&lt;/td&gt;
&lt;td&gt;14m55s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;daft-native&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3m30s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;strong&gt;Ray&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;7m41s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Running on full sized images we get a bit more interesting results:&lt;/p&gt;
&lt;table class="caption-top table"&gt;
&lt;colgroup&gt;
&lt;col style="width: 25%;" /&gt;
&lt;col style="width: 12%;" /&gt;
&lt;col style="width: 12%;" /&gt;
&lt;col style="width: 6%;" /&gt;
&lt;col style="width: 17%;" /&gt;
&lt;col style="width: 25%;" /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr class="header"&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Num_worker&lt;/th&gt;
&lt;th&gt;Pin_memory&lt;/th&gt;
&lt;th&gt;Cache&lt;/th&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;strong&gt;Additional Tests&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;torch&lt;/td&gt;
&lt;td&gt;4m19s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;hf_with_transf&lt;/td&gt;
&lt;td&gt;4m40s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;hf_map&lt;/td&gt;
&lt;td&gt;8m14s, cached: 7m21s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;daft&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3m49s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/section&gt;
&lt;section class="level2" id="developer-experience-dx"&gt;
&lt;h2 class="anchored"&gt;Developer Experience (DX)&lt;/h2&gt;
&lt;div class="tabset-margin-container"&gt;&lt;/div&gt;&lt;div class="panel-tabset"&gt;
&lt;ul class="nav nav-tabs"&gt;&lt;li class="nav-item"&gt;&lt;a class="nav-link active" href="" id="tabset-1-1-tab"&gt;HuggingFace Datasets&lt;/a&gt;&lt;/li&gt;&lt;li class="nav-item"&gt;&lt;a class="nav-link" href="" id="tabset-1-2-tab"&gt;PyTorch “native”&lt;/a&gt;&lt;/li&gt;&lt;li class="nav-item"&gt;&lt;a class="nav-link" href="" id="tabset-1-3-tab"&gt;Daft&lt;/a&gt;&lt;/li&gt;&lt;li class="nav-item"&gt;&lt;a class="nav-link" href="" id="tabset-1-4-tab"&gt;Ray&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;div class="tab-content"&gt;
&lt;div class="tab-pane active" id="tabset-1-1"&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb1" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb1-1"&gt;&lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; test_elem_by_elem(num_workers: &lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;int&lt;/span&gt; &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;|&lt;/span&gt; &lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;None&lt;/span&gt;, pin_memory: &lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;bool&lt;/span&gt; &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;|&lt;/span&gt; &lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;None&lt;/span&gt;, cache: &lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;bool&lt;/span&gt;):&lt;/span&gt;
&lt;span id="cb1-2"&gt;    &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;if&lt;/span&gt; &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;not&lt;/span&gt; cache:&lt;/span&gt;
&lt;span id="cb1-3"&gt;        datasets.disable_caching()&lt;/span&gt;
&lt;span id="cb1-4"&gt;&lt;/span&gt;
&lt;span id="cb1-5"&gt;    &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; _preprocess(data: &lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;dict&lt;/span&gt;):&lt;/span&gt;
&lt;span id="cb1-6"&gt;        imgs &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; [utils.PREPROCESS_TRANSFORMS(x.convert(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"RGB"&lt;/span&gt;)) &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;for&lt;/span&gt; x &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;in&lt;/span&gt; data[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;]]&lt;/span&gt;
&lt;span id="cb1-7"&gt;        data[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;] &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; imgs&lt;/span&gt;
&lt;span id="cb1-8"&gt;        &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; data&lt;/span&gt;
&lt;span id="cb1-9"&gt;&lt;/span&gt;
&lt;span id="cb1-10"&gt;    ds &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; datasets.load_from_disk(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"./imagenette_full_size"&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb1-11"&gt;&lt;/span&gt;
&lt;span id="cb1-12"&gt;    &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; _augment(data: &lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;dict&lt;/span&gt;):&lt;/span&gt;
&lt;span id="cb1-13"&gt;        tensor &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; _preprocess(data[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;])&lt;/span&gt;
&lt;span id="cb1-14"&gt;        data[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;] &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; utils.AUGMENTATIONS(tensor)&lt;/span&gt;
&lt;span id="cb1-15"&gt;        &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; data&lt;/span&gt;
&lt;span id="cb1-16"&gt;&lt;/span&gt;
&lt;span id="cb1-17"&gt;    ds_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"train"&lt;/span&gt;].with_transform(_augment)&lt;/span&gt;
&lt;span id="cb1-18"&gt;    ds_valid &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"validation"&lt;/span&gt;].with_transform(_preprocess)&lt;/span&gt;
&lt;span id="cb1-19"&gt;&lt;/span&gt;
&lt;span id="cb1-20"&gt;    kwargs &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; &lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;dict&lt;/span&gt;(&lt;/span&gt;
&lt;span id="cb1-21"&gt;        num_workers&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;num_workers &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;or&lt;/span&gt; &lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;0&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb1-22"&gt;        persistent_workers&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;bool&lt;/span&gt;(num_workers),&lt;/span&gt;
&lt;span id="cb1-23"&gt;        pin_memory&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;pin_memory,&lt;/span&gt;
&lt;span id="cb1-24"&gt;        batch_size&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;32&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb1-25"&gt;    )&lt;/span&gt;
&lt;span id="cb1-26"&gt;    dls_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; torch.utils.data.DataLoader(ds_train, &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;**&lt;/span&gt;kwargs)&lt;/span&gt;
&lt;span id="cb1-27"&gt;    dls_valid &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; torch.utils.data.DataLoader(ds_valid, &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;**&lt;/span&gt;kwargs)&lt;/span&gt;
&lt;span id="cb1-28"&gt;    &lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-pane" id="tabset-1-2"&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb2" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb2-1"&gt;&lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;class&lt;/span&gt; ImagenetteDataset(Dataset):&lt;/span&gt;
&lt;span id="cb2-2"&gt;    &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; &lt;span class="fu" style="color: #4758AB; background-color: null; font-style: inherit;"&gt;__init__&lt;/span&gt;(&lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;, hf_dataset, preprocess&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;None&lt;/span&gt;, augment&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;None&lt;/span&gt;):&lt;/span&gt;
&lt;span id="cb2-3"&gt;        &lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;.hf_dataset &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; hf_dataset&lt;/span&gt;
&lt;span id="cb2-4"&gt;        &lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;.preprocess &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; preprocess&lt;/span&gt;
&lt;span id="cb2-5"&gt;        &lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;.augment &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; augment&lt;/span&gt;
&lt;span id="cb2-6"&gt;&lt;/span&gt;
&lt;span id="cb2-7"&gt;    &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; &lt;span class="fu" style="color: #4758AB; background-color: null; font-style: inherit;"&gt;__len__&lt;/span&gt;(&lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;):&lt;/span&gt;
&lt;span id="cb2-8"&gt;        &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; &lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;len&lt;/span&gt;(&lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;.hf_dataset)&lt;/span&gt;
&lt;span id="cb2-9"&gt;&lt;/span&gt;
&lt;span id="cb2-10"&gt;    &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; &lt;span class="fu" style="color: #4758AB; background-color: null; font-style: inherit;"&gt;__getitem__&lt;/span&gt;(&lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;, idx):&lt;/span&gt;
&lt;span id="cb2-11"&gt;        data &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; &lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;.hf_dataset[idx]&lt;/span&gt;
&lt;span id="cb2-12"&gt;&lt;/span&gt;
&lt;span id="cb2-13"&gt;        image &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; data[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;].convert(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"RGB"&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb2-14"&gt;&lt;/span&gt;
&lt;span id="cb2-15"&gt;        &lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# Apply preprocessing and augmentation if specified&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-16"&gt;        &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;if&lt;/span&gt; &lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;.preprocess:&lt;/span&gt;
&lt;span id="cb2-17"&gt;            image: torch.Tensor &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; &lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;.preprocess(image)&lt;/span&gt;
&lt;span id="cb2-18"&gt;        &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;if&lt;/span&gt; &lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;.augment:&lt;/span&gt;
&lt;span id="cb2-19"&gt;            image &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; &lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;self&lt;/span&gt;.augment(image)&lt;/span&gt;
&lt;span id="cb2-20"&gt;&lt;/span&gt;
&lt;span id="cb2-21"&gt;        &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; {&lt;/span&gt;
&lt;span id="cb2-22"&gt;            &lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;: image,&lt;/span&gt;
&lt;span id="cb2-23"&gt;            &lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"label"&lt;/span&gt;: data[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"label"&lt;/span&gt;],&lt;/span&gt;
&lt;span id="cb2-24"&gt;        }&lt;/span&gt;
&lt;span id="cb2-25"&gt;train_dataset &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ImagenetteDataset(&lt;/span&gt;
&lt;span id="cb2-26"&gt;    ds[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"train"&lt;/span&gt;], preprocess&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;utils.PREPROCESS_TRANSFORMS&lt;/span&gt;
&lt;span id="cb2-27"&gt;)&lt;/span&gt;
&lt;span id="cb2-28"&gt;&lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;print&lt;/span&gt;(&lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;len&lt;/span&gt;(train_dataset))&lt;/span&gt;
&lt;span id="cb2-29"&gt;valid_dataset &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ImagenetteDataset(&lt;/span&gt;
&lt;span id="cb2-30"&gt;    ds[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"validation"&lt;/span&gt;], preprocess&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;utils.PREPROCESS_TRANSFORMS&lt;/span&gt;
&lt;span id="cb2-31"&gt;)&lt;/span&gt;
&lt;span id="cb2-32"&gt;&lt;/span&gt;
&lt;span id="cb2-33"&gt;&lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# Create DataLoader instances&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb2-34"&gt;kwargs &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; &lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;dict&lt;/span&gt;(&lt;/span&gt;
&lt;span id="cb2-35"&gt;    num_workers&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;num_workers &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;or&lt;/span&gt; &lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;0&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb2-36"&gt;    persistent_workers&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;bool&lt;/span&gt;(num_workers),&lt;/span&gt;
&lt;span id="cb2-37"&gt;    pin_memory&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;pin_memory,&lt;/span&gt;
&lt;span id="cb2-38"&gt;    batch_size&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;32&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb2-39"&gt;)&lt;/span&gt;
&lt;span id="cb2-40"&gt;dls_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; DataLoader(train_dataset, shuffle&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="va" style="color: #111111; background-color: null; font-style: inherit;"&gt;True&lt;/span&gt;, &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;**&lt;/span&gt;kwargs)&lt;/span&gt;
&lt;span id="cb2-41"&gt;dls_valid &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; DataLoader(valid_dataset, &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;**&lt;/span&gt;kwargs)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-pane" id="tabset-1-3"&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb3" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb3-1"&gt;&lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; load_imagenette_datasets_daft(dataset_path&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"./imagenette_full_size"&lt;/span&gt;):&lt;/span&gt;
&lt;span id="cb3-2"&gt;    ds &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; datasets.load_from_disk(dataset_path)&lt;/span&gt;
&lt;span id="cb3-3"&gt;    extract_img_bytes &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; daft.col(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;).struct.get(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"bytes"&lt;/span&gt;).alias(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb3-4"&gt;    ds_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; daft.from_arrow(ds[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"train"&lt;/span&gt;].data.table).select(&lt;/span&gt;
&lt;span id="cb3-5"&gt;        &lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"label"&lt;/span&gt;, extract_img_bytes&lt;/span&gt;
&lt;span id="cb3-6"&gt;    )&lt;/span&gt;
&lt;span id="cb3-7"&gt;&lt;/span&gt;
&lt;span id="cb3-8"&gt;    ds_valid &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; daft.from_arrow(ds[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"validation"&lt;/span&gt;].data.table).select(&lt;/span&gt;
&lt;span id="cb3-9"&gt;        &lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"label"&lt;/span&gt;, extract_img_bytes&lt;/span&gt;
&lt;span id="cb3-10"&gt;    )&lt;/span&gt;
&lt;span id="cb3-11"&gt;&lt;/span&gt;
&lt;span id="cb3-12"&gt;    img_decode_resize &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; (&lt;/span&gt;
&lt;span id="cb3-13"&gt;        daft.col(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;).image.decode(mode&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"RGB"&lt;/span&gt;).image.resize(&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;224&lt;/span&gt;, &lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;224&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb3-14"&gt;    )&lt;/span&gt;
&lt;span id="cb3-15"&gt;&lt;/span&gt;
&lt;span id="cb3-16"&gt;    ds_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds_train.with_column(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;, img_decode_resize)&lt;/span&gt;
&lt;span id="cb3-17"&gt;    ds_valid &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds_valid.with_column(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;, img_decode_resize)&lt;/span&gt;
&lt;span id="cb3-18"&gt;&lt;/span&gt;
&lt;span id="cb3-19"&gt;    &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; to_f32_tensor(ds: daft.DataFrame):&lt;/span&gt;
&lt;span id="cb3-20"&gt;        &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; ds.with_column(&lt;/span&gt;
&lt;span id="cb3-21"&gt;            &lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb3-22"&gt;            daft.col(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;).&lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;apply&lt;/span&gt;(&lt;/span&gt;
&lt;span id="cb3-23"&gt;                &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;lambda&lt;/span&gt; x: (x &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;/&lt;/span&gt; &lt;span class="fl" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;255.0&lt;/span&gt;).transpose(&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;2&lt;/span&gt;, &lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;0&lt;/span&gt;, &lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;1&lt;/span&gt;),&lt;/span&gt;
&lt;span id="cb3-24"&gt;                return_dtype&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;daft.DataType.tensor(daft.DataType.float32()),&lt;/span&gt;
&lt;span id="cb3-25"&gt;            ),&lt;/span&gt;
&lt;span id="cb3-26"&gt;        )&lt;/span&gt;
&lt;span id="cb3-27"&gt;&lt;/span&gt;
&lt;span id="cb3-28"&gt;    ds_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; to_f32_tensor(ds_train)&lt;/span&gt;
&lt;span id="cb3-29"&gt;    ds_valid &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; to_f32_tensor(ds_valid)&lt;/span&gt;
&lt;span id="cb3-30"&gt;&lt;/span&gt;
&lt;span id="cb3-31"&gt;    ds_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds_train.to_torch_iter_dataset()&lt;/span&gt;
&lt;span id="cb3-32"&gt;    ds_valid &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds_valid.to_torch_iter_dataset()&lt;/span&gt;
&lt;span id="cb3-33"&gt;&lt;/span&gt;
&lt;span id="cb3-34"&gt;    dls_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; torch.utils.data.DataLoader(ds_train, batch_size&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;32&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb3-35"&gt;    dls_valid &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; torch.utils.data.DataLoader(ds_valid, batch_size&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;32&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb3-36"&gt;    &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; dls_train, dls_valid&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-pane" id="tabset-1-4"&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb4" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb4-1"&gt;&lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; load_imagenette_datasets_ray(dataset_path&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"./imagenette_full_size"&lt;/span&gt;):&lt;/span&gt;
&lt;span id="cb4-2"&gt;    &lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# Load the Arrow dataset with Ray&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-3"&gt;    ds &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; datasets.load_from_disk(dataset_path)&lt;/span&gt;
&lt;span id="cb4-4"&gt;&lt;/span&gt;
&lt;span id="cb4-5"&gt;    &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; extract_img_to_pil(data):&lt;/span&gt;
&lt;span id="cb4-6"&gt;        image &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; data[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;][&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"bytes"&lt;/span&gt;]&lt;/span&gt;
&lt;span id="cb4-7"&gt;        data[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;] &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; PIL.Image.&lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;open&lt;/span&gt;(io.BytesIO(image)).convert(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"RGB"&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb4-8"&gt;        &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; data&lt;/span&gt;
&lt;span id="cb4-9"&gt;&lt;/span&gt;
&lt;span id="cb4-10"&gt;    ds_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ray.data.from_huggingface(ds[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"train"&lt;/span&gt;]).&lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;map&lt;/span&gt;(extract_img_to_pil)&lt;/span&gt;
&lt;span id="cb4-11"&gt;    ds_val &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ray.data.from_huggingface(ds[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"validation"&lt;/span&gt;]).&lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;map&lt;/span&gt;(extract_img_to_pil)&lt;/span&gt;
&lt;span id="cb4-12"&gt;&lt;/span&gt;
&lt;span id="cb4-13"&gt;    preprocess_transforms &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; transforms.Compose(&lt;/span&gt;
&lt;span id="cb4-14"&gt;        [&lt;/span&gt;
&lt;span id="cb4-15"&gt;            utils.PREPROCESS_TRANSFORMS,&lt;/span&gt;
&lt;span id="cb4-16"&gt;        ]&lt;/span&gt;
&lt;span id="cb4-17"&gt;    )&lt;/span&gt;
&lt;span id="cb4-18"&gt;    augmentation_transforms &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; utils.AUGMENTATIONS&lt;/span&gt;
&lt;span id="cb4-19"&gt;&lt;/span&gt;
&lt;span id="cb4-20"&gt;    &lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# Apply transformations in Ray&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-21"&gt;    &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; preprocess_image(batch):&lt;/span&gt;
&lt;span id="cb4-22"&gt;        batch[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;] &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; [preprocess_transforms(x) &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;for&lt;/span&gt; x &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;in&lt;/span&gt; batch[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;]]&lt;/span&gt;
&lt;span id="cb4-23"&gt;&lt;/span&gt;
&lt;span id="cb4-24"&gt;        &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; batch&lt;/span&gt;
&lt;span id="cb4-25"&gt;&lt;/span&gt;
&lt;span id="cb4-26"&gt;    &lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; augment_image(elem):&lt;/span&gt;
&lt;span id="cb4-27"&gt;        elem[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;] &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; augmentation_transforms(elem[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;])&lt;/span&gt;
&lt;span id="cb4-28"&gt;&lt;/span&gt;
&lt;span id="cb4-29"&gt;        &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; batch&lt;/span&gt;
&lt;span id="cb4-30"&gt;&lt;/span&gt;
&lt;span id="cb4-31"&gt;    ds_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds_train.map_batches(preprocess_image).&lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;map&lt;/span&gt;(augment_image)&lt;/span&gt;
&lt;span id="cb4-32"&gt;&lt;/span&gt;
&lt;span id="cb4-33"&gt;    ds_val &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds_val.map_batches(preprocess_image)&lt;/span&gt;
&lt;span id="cb4-34"&gt;&lt;/span&gt;
&lt;span id="cb4-35"&gt;    d_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds_train.to_torch(&lt;/span&gt;
&lt;span id="cb4-36"&gt;        label_column&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"label"&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb4-37"&gt;        batch_size&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;32&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb4-38"&gt;        local_shuffle_buffer_size&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;512&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb4-39"&gt;        prefetch_batches&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;5&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb4-40"&gt;    )&lt;/span&gt;
&lt;span id="cb4-41"&gt;    d_valid &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds_val.to_torch(label_column&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"label"&lt;/span&gt;, batch_size&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;32&lt;/span&gt;, prefetch_batches&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;5&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb4-42"&gt;&lt;/span&gt;
&lt;span id="cb4-43"&gt;    &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; d_train, d_valid&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I think most of the frameworks ends up at a similar place in the experience.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Quick DX Ranking&lt;/strong&gt;&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;PyTorch &amp;amp; HuggingFace Datasets&lt;/li&gt;
&lt;li&gt;Daft&lt;/li&gt;
&lt;li&gt;Ray (albeit I believe it to be the most scalable solution as you can truly tinker in detail)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I enjoyed &lt;em&gt;Daft&lt;/em&gt; a lot with its multi-modal syntax, inspired by polars with namespaces (e.g.&amp;nbsp;&lt;code&gt;.image.decode()&lt;/code&gt;), which can be phenomenal. Working with DataFrame’s is a cool addition, where you can drop into python simply by using &lt;code&gt;apply&lt;/code&gt;.&lt;br /&gt;
Working with &lt;em&gt;Daft&lt;/em&gt; more and more I noticed that the DataFrame syntax sometimes becomes a big blocker and the simplicity of HF Datasets and Ray using &lt;code&gt;dict&lt;/code&gt;’s in &lt;code&gt;.map&lt;/code&gt; statements results in easier code and smoother integration with existing libraries.&lt;br /&gt;
Additionally HF Datasets / PyTorch DataLoaders feels more pythonic, where the latter is real simple. I can’t put my finger on it but they just seem easier to debug and understand.&lt;/p&gt;
&lt;p&gt;It’ll sure be interesting to follow the progress being made, and I’m happy the dust isn’t settled yet!&lt;/p&gt;


&lt;/section&gt;</description><author>Londogard Blog</author><pubDate>Tue, 03 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.londogard.com/posts/2024-12-03-data-loading-comparison/</guid></item><item><title>The Timeless Way of Software</title><link>https://youtu.be/wTv5kvuP1hI?si=9LxA2PoSrh1E8O3l</link><description>In this talk, we'll cover "living" systems, sustainable design, pattern languages, and practical toolkits for experimenting with the principles.</description><author>taylor.town</author><pubDate>Tue, 03 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://youtu.be/wTv5kvuP1hI?si=9LxA2PoSrh1E8O3l</guid></item><item><title>Self-guaranteeing promises</title><link>https://stephango.com/self-guarantee</link><description>&lt;p&gt;Companies break promises all the time. A self-guaranteeing promise is verifiable and non-reversible. It does not require you to trust anyone.&lt;/p&gt;

&lt;p&gt;&lt;a class="internal-link" href="/file-over-app"&gt;File over app&lt;/a&gt; is a self-guaranteeing promise. If files are in your control, in an open format, you can use those files in another app at any time. Not an export. The exact same files. It’s good practice to test this with any self-proclaimed file-over-app app you use.&lt;/p&gt;

&lt;p&gt;“Stainless steel” is a self-guaranteeing promise. You can use water to test any tool that makes this promise. The stainlessness of steel cannot be withdrawn.&lt;/p&gt;

&lt;p&gt;Terms and policies are not self-guaranteeing. A company may promise the privacy of your data, but those policies can change at any time. Changes can retroactively affect data you have spent years putting into the tool. Examples: &lt;a href="https://x.com/kepano/status/1682829662370557952"&gt;Google&lt;/a&gt;, &lt;a href="https://x.com/kepano/status/1688606865058574339"&gt;Zoom&lt;/a&gt;, &lt;a href="https://x.com/kepano/status/1735032935336829230"&gt;Dropbox&lt;/a&gt;, &lt;a href="https://x.com/kepano/status/1762864738499952756"&gt;Tumblr&lt;/a&gt;, &lt;a href="https://x.com/kepano/status/1791266503456907554"&gt;Slack&lt;/a&gt;, &lt;a href="https://x.com/kepano/status/1798459810981220621"&gt;Adobe&lt;/a&gt;, &lt;a href="https://x.com/kepano/status/1808167319694368999"&gt;Figma&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A self-guaranteeing promise about privacy gives you proof that the tool cannot access your data in the first place.&lt;/p&gt;

&lt;p&gt;Encoding values into a governance structure is not self-guaranteeing. Given enough motivation, the corporate structure can be reversed. The structure is not in your hands. Example: &lt;a href="https://en.wikipedia.org/wiki/Removal_of_Sam_Altman_from_OpenAI"&gt;OpenAI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Open source &lt;em&gt;alone&lt;/em&gt; is not self-guaranteeing. Even open source apps can rely on data that is stuck in databases or in proprietary formats that are difficult to switch away from. Open source is not a reliable safeguard against the biases of &lt;a class="internal-link" href="/vcware"&gt;venture capital&lt;/a&gt;. Examples: &lt;a href="https://x.com/kepano/status/1851555417165598790"&gt;Omnivore&lt;/a&gt;, &lt;a href="https://news.ycombinator.com/item?id=39396130"&gt;Skiff&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When you choose a tool, the future of that tool is always ambiguous. On a long enough timeline the substrate changes. Your needs change, the underlying operating system changes, the company goes out of business or gets acquired, better options come along.&lt;/p&gt;

&lt;p&gt;It is possible to accept the ambiguousness of a tool’s future if you choose tools that make self-guaranteeing promises.&lt;/p&gt;</description><author>Steph Ango</author><pubDate>Tue, 03 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://stephango.com/self-guarantee</guid></item><item><title>Trying out logs and TILs</title><link>https://blog.erethon.com/blog/2024/12/03/trying-out-logs-and-tils/</link><description>&lt;p&gt;
I often find myself wanting to blog about something, but I get stuck on trying
to write a blog post that's &lt;em&gt;perfect&lt;/em&gt;, which is a big mental barrier. The longer
it takes me to publish something, the harder it gets, hence why I haven't posted
about my updates since May. I'm also guilty of having multiple long essay-like
posts, that are half-finished for years now, which apply a constant pressure in
my posts backlog, making the situation worse.&lt;/p&gt;
&lt;p&gt;
As an attempt to fix this, I'm giving TILs and log entries a try.&lt;/p&gt;</description><author>Erethon's Corner</author><pubDate>Tue, 03 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.erethon.com/blog/2024/12/03/trying-out-logs-and-tils/</guid></item><item><title>Failover Replication Slots with Postgres 17</title><link>https://www.morling.dev/blog/failover-replication-slots-with-postgres-17/</link><description>&lt;div class="toc" id="toc"&gt;
&lt;div id="toctitle"&gt;Table of Contents&lt;/div&gt;
&lt;ul class="sectlevel1"&gt;
&lt;li&gt;&lt;a href="#_hello_failover_slots"&gt;Hello, Failover Slots!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_failover_slots_in_decodable"&gt;Failover Slots in Decodable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_wrapping_up"&gt;Wrapping Up&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;em&gt;This post originally appeared on the &lt;a href="https://www.decodable.co/blog/failover-replication-slots-with-postgres-17"&gt;Decodable blog&lt;/a&gt;. All rights reserved.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Postgres read replicas are commonly used not only to distribute query load amongst multiple nodes, but also to ensure high availability (HA) of the database.
If the primary node of a Postgres cluster fails, a read replica can be promoted to be the new primary, processing write (and read) requests from thereon.&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Tue, 03 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/failover-replication-slots-with-postgres-17/</guid></item><item><title>How much space do Montreal's cars take?</title><link>https://www.davidschlachter.com/misc/montreal-how-many-cars-area</link><description>How much surface area is occupied by cars in Montreal? I make an estimate.</description><author>David Schlachter</author><pubDate>Tue, 03 Dec 2024 01:50:00 GMT</pubDate><guid isPermaLink="true">https://www.davidschlachter.com/misc/montreal-how-many-cars-area</guid></item><item><title>An Anxiety Attack in Daytona Beach</title><link>https://digitalnomadder.micro.blog/2024/12/02/an-anxiety-attack.html</link><description>&lt;p&gt;We took a quick trip to Daytona beach to see my friend’s band  play at Punk Rock Pizza.&lt;/p&gt;
&lt;img alt="" height="600" src="https://cdn.uploads.micro.blog/79953/2024/34730cf060.jpg" width="337" /&gt;
&lt;p&gt;He is the lead singer of &lt;a href="https://www.instagram.com/anxietyattack.fl/profilecard/?igsh=NGZsajNrZHJkY3M4"&gt;Anxiety Attack&lt;/a&gt;.  It’s an independent band.&lt;/p&gt;
&lt;img alt="" height="600" src="https://cdn.uploads.micro.blog/79953/2024/7e40541dc5.jpg" width="337" /&gt;
&lt;p&gt;I first met the lead singer as the bar tender downstairs.  I just started going down to the bar to hang out with him on the weekend and found out he was in a band.&lt;/p&gt;
&lt;p&gt;It was fun, different and the food was good at Pirate’s Pizza.&lt;/p&gt;</description><author>The Digital Nomad</author><pubDate>Tue, 03 Dec 2024 01:48:48 GMT</pubDate><guid isPermaLink="true">https://digitalnomadder.micro.blog/2024/12/02/an-anxiety-attack.html</guid></item><item><title>A talk on Polar Gothic literature at Chimära Bokhandel</title><link>https://liza.io/a-talk-on-polar-gothic-literature-at-chim%C3%A4ra-bokhandel/</link><description>&lt;p&gt;This evening I went to another presentation at &lt;a href="https://www.chimara.se/"&gt;Chimära Bokhandel &amp;amp; Butik&lt;/a&gt;. The topic this time was Arthur Conan Doyle&amp;rsquo;s story &lt;em&gt;The Captain of the Polestar&lt;/em&gt; and Polar Gothic literature.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Mon, 02 Dec 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/a-talk-on-polar-gothic-literature-at-chim%C3%A4ra-bokhandel/</guid></item><item><title>Advent of Code 2024 Day 2: Parsing Expression Grammars</title><link>https://fbrs.io/aoc2024-d2/</link><description>&lt;p&gt;I am fully committed to using Janet this year! At least I hope so. One of my goals is to get really familiar and fluent with using parsing expression grammars (PEG). I’ve always liked parser combinators, which seem to be more or less the same thing. One of the most basic and typical ways to parse input in Advent of Code is to split a string into a lines and split each line on spaces.&lt;/p&gt;
&lt;pre class="language-text "&gt;&lt;code class="language-text"&gt;1 2 3
4 5 6
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This an be achieved quite elegantly with a somewhat more recent addition to the PEG library, &lt;code&gt;split&lt;/code&gt;. You give it two patterns, one for the separator and one for the actual contents, and it generally does the right thing. There is just one caveat. In the following snippet, there’s a space at the end of my input string. If you remove the asterisk &lt;code&gt;*&lt;/code&gt; from the digit pattern, you get no matches. So &lt;code&gt;split&lt;/code&gt; more or less requires you to make the content optional, unless you are absolutely certain that you won’t have a dangling separator at the end of your input.&lt;/p&gt;
&lt;pre class="language-janet "&gt;&lt;code class="language-janet"&gt;  (peg&amp;#x2f;match ~(split :s &amp;#x27;:d*) &amp;quot;1 2 3 &amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By making the second pattern optional, the end of the string, “3 “ can now be matched as two digits separated by a space. The second digit is simply empty. Sounds totally logical, I know. But you probably don’t want an empty capture. You can either remove it later through &lt;code&gt;filter&lt;/code&gt; or you can call &lt;code&gt;string/trimr&lt;/code&gt; on the input, which is my convention for AoC. This removes trailing whitespace, so you no longer need to make the second pattern optional, like this:&lt;/p&gt;
&lt;pre class="language-janet "&gt;&lt;code class="language-janet"&gt;(peg&amp;#x2f;match ~(split :s &amp;#x27;:d) (string&amp;#x2f;trimr &amp;quot;1 2 3 &amp;quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Of course you’ll have to adjust your code for other separators (or &lt;code&gt;filter&lt;/code&gt; it out later).&lt;/p&gt;
&lt;p&gt;Anyway, back to the real world (of AoC). Here’s the same PEG with and without using &lt;code&gt;split&lt;/code&gt;. I think it’s a huge improvement.&lt;/p&gt;
&lt;pre class="language-janet "&gt;&lt;code class="language-janet"&gt;# before
(def input-peg
  (peg&amp;#x2f;compile
    ~{:main (* (some (* :line (? :s))) -1)
      :line (group (some (* :num (any &amp;quot; &amp;quot;))))
      :num (&amp;#x2f; (&amp;lt;- :d+) ,scan-number)}))

# after
(def parser
  (peg&amp;#x2f;compile ~(split &amp;quot;\n&amp;quot; (group (split :s (any (number :d+)))))))
&lt;/code&gt;&lt;/pre&gt;</description><author/><pubDate>Mon, 02 Dec 2024 10:08:55 GMT</pubDate><guid isPermaLink="true">https://fbrs.io/aoc2024-d2/</guid></item><item><title>What I've learned running Linux and Windows off of USB flash drives</title><link>https://ounapuu.ee/posts/2024/12/02/linux-on-usb/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/12/02/linux-on-usb/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;I&amp;rsquo;ve recently made an effort to reduce the amount of tech stuff that I own. Lack of space, lack of time and the guilt
of storing perfectly functional hardware unused in a box were the main motivators.&lt;/p&gt;
&lt;p&gt;This has resulted in experiments with my assortment of USB flash storage that I&amp;rsquo;ve acquired over the years. They&amp;rsquo;re too
old and cheap to be worth selling, so why not run them to the ground?&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve always liked the idea of USB sticks. They&amp;rsquo;re small and the good ones are actually surprisingly fast, especially
compared to hard drives. This is why I started installing operating systems on them.
It&amp;rsquo;s really handy to have a USB stick around with a full operating system on it for testing purposes, and I&amp;rsquo;m too cheap
to buy an &lt;a href="https://www.iodd.shop/Home"&gt;IODD device.&lt;/a&gt;&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;What I&amp;rsquo;ve learned is that Linux installed on a USB stick is a much nicer experience compared to a Windows To Go
installation, even on the cheaper and crappier ones.&lt;/p&gt;
&lt;h3 id="the-windows-experience"&gt;The Windows experience&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve used &lt;a href="https://rufus.ie/en/"&gt;Rufus&lt;/a&gt; for creating portable Windows installations in the past, mostly on portable
SSD-s.
These have come in handy when I&amp;rsquo;ve had to update the firmware on certain PC-s or associated hardware, like USB-C
docks.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Recently, I took a Samsung FIT 128GB USB 3.0 drive to see how well Windows can operate off of it.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s horrible.&lt;/p&gt;
&lt;p&gt;Windows will run, but the first time you boot it, you will have to wait for all sorts of driver and update
installations,
which puts a huge strain on the storage, making the whole experience borderline unusable. Once that process is
complete (multiple hours later), it&amp;rsquo;s actually not that bad, until you receive yet another Windows update.&lt;/p&gt;
&lt;p&gt;For a USB stick that you use only occasionally to troubleshoot hardware, Windows is a really poor choice. Even on a
proper portable SSD, it will still be relatively slow due to all that driver and update installation
taking place in the background.&lt;/p&gt;
&lt;p&gt;Eventually, that installation will also probably brick itself in one way or another, and not even due to any storage
failures. Simply using the same disk on multiple machines will accrue configuration and driver cruft that likely results
in Windows giving up. It&amp;rsquo;s just a guesstimate on my side, but I would not be surprised if it was true.&lt;/p&gt;
&lt;p&gt;After a Windows update run completely fried one USB stick&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt; that I used for testing, I gave up on it.&lt;/p&gt;
&lt;h3 id="the-linux-experience"&gt;The Linux experience&lt;/h3&gt;
&lt;p&gt;Most Linux distributions have supported a LiveCD/LiveUSB solution for at least 15+ years at this point. It&amp;rsquo;s a neat
idea: your installation media also doubles as an emergency portable installation of that Linux distro, which you can use
to quickly test the functionality of a PC, repair an existing installation or install it on a new machine.&lt;/p&gt;
&lt;p&gt;The downside of that solution is that the software on it cannot be changed. You can temporarily install new software or
update existing packages during that session, or have a separate writeable partition set aside on the USB stick for
storing any permanent changes, but it&amp;rsquo;s a bit of a hassle to think about that nuance every time.&lt;/p&gt;
&lt;p&gt;This is why I went ahead and installed Fedora Linux 40 on a Sandisk Ultra 16GB USB 3.0 flash drive &lt;a href="https://ounapuu.ee/posts/2023/02/15/shrinkflation/"&gt;(the
one that is actually 16GB).&lt;/a&gt;
The storage speed is still a bottleneck, but it&amp;rsquo;s much less noticeable during normal use.&lt;/p&gt;
&lt;p&gt;After the installation is done, you&amp;rsquo;re simply using the system as-is. Opening programs will have a slightly longer
delay, but it&amp;rsquo;s still a perfectly &lt;a href="https://en.wiktionary.org/wiki/cromulent"&gt;cromulent&lt;/a&gt; Linux experience.&lt;/p&gt;
&lt;p&gt;A fresh installation of Fedora Workstation 40 uses only &lt;strong&gt;3.6GB&lt;/strong&gt; of storage! On a 16GB USB flash drive, this leaves
plenty
of room for performing system updates and installing a small number of applications. If you choose the &lt;code&gt;btrfs&lt;/code&gt;
filesystem during the installation (the default option), then the files will also be compressed to save even more space
and increase the effective storage bandwidth.&lt;sup id="fnref:4"&gt;&lt;a class="footnote-ref" href="#fn:4"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/12/02/linux-on-usb/media/fedora-disk-usage.png"&gt;
        &lt;img alt="Disk usage on a fresh Fedora Workstation 40 installation." height="264" src="https://ounapuu.ee/posts/2024/12/02/linux-on-usb/media/fedora-disk-usage_hu_32344c8ad038f582.png" style="width: auto; height: auto; border-radius: 8px;" width="684" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Disk usage on a fresh Fedora Workstation 40 installation.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;You&amp;rsquo;ll only notice that you&amp;rsquo;re running off of a USB flash drive when installing new packages, performing system updates
or downloading large files. At least with Linux you have control over &lt;em&gt;when&lt;/em&gt; you want to install the updates.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/12/02/linux-on-usb/media/fedora-update.png"&gt;
        &lt;img alt="It's not terribly fast, but it's not terrible!" height="599" src="https://ounapuu.ee/posts/2024/12/02/linux-on-usb/media/fedora-update_hu_551dc5f296185543.png" style="width: auto; height: auto; border-radius: 8px;" width="870" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      It's not terribly fast, but it's not terrible!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Using &lt;code&gt;btrfs&lt;/code&gt; also allows you to easily check the flash drive for any integrity issues. Simply
run &lt;code&gt;sudo btrfs scrub start -B /&lt;/code&gt;
and you&amp;rsquo;ll know soon enough if your flash drive is messing with your data.&lt;/p&gt;
&lt;h3 id="the-backerupper-usb-stick"&gt;The &lt;em&gt;backerupper&lt;/em&gt; USB stick&lt;/h3&gt;
&lt;p&gt;One actual use case that I have for a full Linux installation on a USB stick is the &lt;em&gt;backerupper&lt;/em&gt; solution: a fully
featured Fedora Linux installation, stored on a small USB stick, and with limited access to networked storage.&lt;/p&gt;
&lt;p&gt;Most people working in IT know the joys of working with hardware that family members and relatives own. Before doing any
destructive changes, it&amp;rsquo;s always a good idea to make a full disk copy of the storage on the machine you&amp;rsquo;re fixing. If
you screw up, you&amp;rsquo;ll have a way of setting everything back just as it was before you did any work on it.
A full disk image will also mean that nothing will be left behind before you do a fresh installation.&lt;/p&gt;
&lt;p&gt;I already have a home server with some spare storage, so using it as a target for any backups makes perfect sense to me.&lt;/p&gt;
&lt;p&gt;This solution has already come in handy once when I had to deal with a 10-year-old laptop with a dying hard drive.&lt;sup id="fnref:5"&gt;&lt;a class="footnote-ref" href="#fn:5"&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id="crappy-storage-is-still-crappy"&gt;Crappy storage is still crappy&lt;/h3&gt;
&lt;p&gt;Don&amp;rsquo;t expect any kind of longevity with USB sticks, the wear leveling and endurance is not on the level of proper
SSD-s. Linux is nicer to the USB stick regarding the wear and tear, but given enough time, the USB flash drive will
still fail.&lt;/p&gt;
&lt;p&gt;I will still keep using USB flash sticks for this purpose. I have backups of the USB stick installations on my server,
so restoring it will simply mean getting another USB stick and writing the full disk image back to it.&lt;/p&gt;
&lt;p&gt;USB sticks are also great for specialized solutions that don&amp;rsquo;t do a lot of IO
operations. &lt;a href="https://libreelec.tv/"&gt;LibreELEC&lt;/a&gt;, a media-center oriented Linux distro, can be run off of a USB flash
drive without any major issues, since its storage demands are very small (unless you install a ton of plugins).&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Linux and USB flash storage is a great option for making backups or having a working operating system available for
disaster recovery scenarios. Just mind the endurance of the flash storage, and don&amp;rsquo;t use Windows.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I won&amp;rsquo;t trust Ventoy &lt;a href="https://github.com/ventoy/Ventoy/issues/2795"&gt;until they address this issue.&lt;/a&gt;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Linux can also install firmware updates on some machines and hardware, but it&amp;rsquo;s not a guaranteed success.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;only the third time that I&amp;rsquo;ve actually fried a solid state flash storage device through write abuse in my life.
Flash is actually pretty good!&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;compression allows us to push more data through a small pipe, be it a slow storage device or the network, at the
cost of additional CPU usage.&amp;#160;&lt;a class="footnote-backref" href="#fnref:4"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;I utilized my sick tech skills to force a full rewrite of the disk to reduce the number of pending bad sectors,
and that made Windows happy enough to run on it once again with a fresh installation. It&amp;rsquo;s a ticking time bomb, but the
user is well aware of it and just wants to have a working backup laptop around.&amp;#160;&lt;a class="footnote-backref" href="#fnref:5"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>./techtipsy</author><pubDate>Mon, 02 Dec 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/12/02/linux-on-usb/</guid></item><item><title>Exploring the Model Context Protocol with Deno 2 and Playwright</title><link>https://shruggingface.com/microblog/2024/12/02/exploring-the-model-context-protocol-with-deno-2-and-playwright</link><author>shruggingface.com</author><pubDate>Mon, 02 Dec 2024 05:13:00 GMT</pubDate><guid isPermaLink="true">https://shruggingface.com/microblog/2024/12/02/exploring-the-model-context-protocol-with-deno-2-and-playwright</guid></item><item><title>Every Mantra has a Counter-Mantra</title><link>https://olshansky.info/posts/every-mantra-has-a-counter-mantra/</link><description>Including this one</description><author>🦉 olshansky 🦁</author><pubDate>Mon, 02 Dec 2024 02:21:51 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/every-mantra-has-a-counter-mantra/</guid></item><item><title>Evolving my ergonomic setup (or, my laptop with extra steps)</title><link>https://ntietz.com/blog/evolving-ergo-setup/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;My computer setup attracts attention when I'm out and about.
This has two effects: engineers&lt;sup class="footnote-reference" id="fr-engineers-1"&gt;&lt;a href="https://ntietz.com/blog/evolving-ergo-setup/#fn-engineers"&gt;[1]&lt;/a&gt;&lt;/sup&gt; ask me about it, and everyone else ignores me.
These effects are not undesirable, but further testing is required.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;iteration4a.d8a061895f3a2222.jpg" /&gt;
&lt;p&gt;The &lt;em&gt;main&lt;/em&gt; reason I have such an unusual setup, though, is more practical: so my arms/hands do not hurt from using my laptop.
I wrote about the &lt;a href="https://ntietz.com/blog/my-portable-ergonomic-setup/"&gt;second iteration&lt;/a&gt; before, and a lot has changed since then.
Now it's time to record all the previous iterations&lt;sup class="footnote-reference" id="fr-scrap-wood-1"&gt;&lt;a href="https://ntietz.com/blog/evolving-ergo-setup/#fn-scrap-wood"&gt;[2]&lt;/a&gt;&lt;/sup&gt; and then detail what's new.&lt;/p&gt;
&lt;h1 id="prototypes-1-through-3"&gt;Prototypes 1 through 3&lt;/h1&gt;
&lt;h2 id="prototype-1-chonky-tray"&gt;Prototype 1: chonky tray&lt;/h2&gt;
&lt;p&gt;The first prototype is what I lovingly call the chonky tray.
It was really a portable desk, with a recess routed into it so that the laptop stand could sit inside of it.
This prevented that from sliding off, mostly, and gave a flush surface under it for the keyboard.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;iteration1.364479210f9a8f2f.png" /&gt;
&lt;p&gt;This setup worked, sort of.
I could use my keyboard in my armchair in our living room—but I could &lt;em&gt;not&lt;/em&gt; use it in the armchair in my office, because it was too wide!&lt;/p&gt;
&lt;p&gt;It was also way too heavy and too large to move around the house, let alone anywhere &lt;em&gt;outside&lt;/em&gt; the house.
This first prototype really served only to ignite my desire to have something, and to show me some things that don't work.
But I kept hurting myself by &lt;em&gt;not&lt;/em&gt; using it.&lt;/p&gt;
&lt;h2 id="prototype-2-luggable-in-backpack"&gt;Prototype 2: luggable in backpack&lt;/h2&gt;
&lt;p&gt;The second prototype is the one that was detailed in my previous blog post.
It was motivated by this realization that I was going to visit &lt;a href="https://www.recurse.com/"&gt;the Recurse Center&lt;/a&gt;, a programming nerd enclave in NYC, and could &lt;em&gt;not&lt;/em&gt; take my laptop with me since I had no portable way to use it without injury&lt;sup class="footnote-reference" id="fr-except-talon-1"&gt;&lt;a href="https://ntietz.com/blog/evolving-ergo-setup/#fn-except-talon"&gt;[3]&lt;/a&gt;&lt;/sup&gt;.
So, I made this!&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;iteration2.b708c80bdff1592d.jpg" /&gt;
&lt;p&gt;This one worked pretty well for me!
It let me fit my whole setup in my oversized backpack, and I could easily take it out of there and use it on the go.
I used it on the train ride up to NYC, inside RC's headquarters, and in coffeeshops.&lt;/p&gt;
&lt;p&gt;But it wasn't perfect.
It was a bit heavy, though with the keyboard I was using, there wasn't much to do about that.
Getting my laptop in and out was annoying, since I had to unscrew a nut before sliding it out.
And the rigid mounts for the keyboards were &lt;em&gt;okay&lt;/em&gt;: they worked, but lacked some adjustability.&lt;/p&gt;
&lt;p&gt;The biggest problem, though, was that it did &lt;em&gt;not&lt;/em&gt; fit in my personal item bag.
And my oversized backpack doesn't fit under all airline seats.
And I'd just been accepted to speak at a conference.&lt;/p&gt;
&lt;p&gt;Uh oh.&lt;/p&gt;
&lt;h2 id="interlude-fever-dreams"&gt;Interlude: fever dreams&lt;/h2&gt;
&lt;p&gt;Around this time is when I got very sick.
I'm recovering now, and I'll write more about it all another time—it's quite the story.
But the relevant bit here is that I spent a lot of time during the summer in bed or lying down for radiology procedures.&lt;/p&gt;
&lt;p&gt;A funny thing happens when I have to lie down and rest for long periods of time: I start designing things.
I was extremely fatigued and had near constant pain, and designing something was a perfect way to move through all of that.&lt;/p&gt;
&lt;p&gt;One potential design, using a lot of steel and magnets, came from a morning at home in bed.
But the actual next iteration came from a two hour radiology procedure.
For that one, I had my arms pinned at my sides and could do little except stare up at the ceiling while they took images&lt;sup class="footnote-reference" id="fr-crowdstrike-1"&gt;&lt;a href="https://ntietz.com/blog/evolving-ergo-setup/#fn-crowdstrike"&gt;[4]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;And so I designed what you'll see next!
There are some design flaws, but I'm proud of the experimentation, and it was a crucial step toward my current iteration.
And it was some good practice using HeadCAD for a design, then seeing if I could transform that design into a real object.&lt;/p&gt;
&lt;h2 id="prototype-3-fabric-hinges"&gt;Prototype 3: fabric hinges&lt;/h2&gt;
&lt;p&gt;The third prototype is the one I took to give that conference talk.
It was interesting, a successful experiment, and it lasted about for the duration of that trip.&lt;/p&gt;
&lt;p&gt;The successes were that it was &lt;em&gt;very&lt;/em&gt; light and had a much thinner base.
It also had a modular design, and I ended up reusing the laptop holder on my current iteration, so that design I'm quite happy with.
And I started using hook-and-loop fasteners on it for attaching peripherals!&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;iteration3a.0abf9991f8601d6c.jpg" /&gt;
&lt;p&gt;One of the coolest things I did here was design some hinges out of fabric, with hook-and-loop for adjustment.
They started out rock-solid, and the design worked better than I expected!
I did have to make sure to align things in a particular direction with the fabric to ensure there was no stretch in the hinging direction.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;iteration3b.bc5422e4c6b429d2.jpg" /&gt;
&lt;p&gt;Unfortunately, since I made things out of thin wood and fabric to keep weight low and keep adjustability high, it started to fail early.
The glue I used started to fail on one of the keyboard hinges, making the whole thing a bit floppy (the other one stayed rock solid and I still really like that design, I just need to figure out the glue situation).&lt;/p&gt;
&lt;p&gt;I also learned that the thinner base isn't entirely a benefit.
With the reduction in total height, it let my shoulder drop down a little more and brought back some of my prior nerve issues!
This actually led my physical therapist to discovering the cause of them, which is a win.
But it influenced the next design.&lt;/p&gt;
&lt;p&gt;The biggest problem, though, was that while it packs up for travel, doing that &lt;em&gt;requires&lt;/em&gt; disassembly.
This was not a fast process, taking at least a few minutes to tear down or put back up.&lt;/p&gt;
&lt;h1 id="latest-iteration-tray-one-tray-dot-one"&gt;Latest iteration: Tray.One ("tray dot one")&lt;/h1&gt;
&lt;p&gt;The fourth prototype is the current one I'm on, and it has addressed the prior issues for me.
I've been using this one for a little while now and there is almost nothing I want to change about it, so I'm done experimenting and might make a nicer version of the same design.&lt;/p&gt;
&lt;p&gt;This one really nails it for me: it's light, it is more ergonomic (for me), it's usable in more places, and it's far easier to travel with.&lt;/p&gt;
&lt;p&gt;The fully deployed configuration looks similar to some previous iterations, but you'll note the keyboard is different.
Also that it looks like everything is scrunched forward on the tray.
I move the peripherals forward and back sometimes to have more variety in positioning, and here they happen to be further back since I was taking photos and things slid a bit.&lt;/p&gt;
&lt;p&gt;I abandoned my Keyboardio Model 100 for a Corne keyboard, which suits me a little better right now.
This was a big change at first but only took a week or two to get back to full speed.
The main advantages for me here are that it's smaller, lighter, and has lower force required per key, so I end up moving my fingers a lot less.
This keyboard &lt;em&gt;doesn't&lt;/em&gt; have mouse support like the Keyboardios do, though, so I picked up a Ploopy trackball mouse and have been loving it.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;iteration4a.d8a061895f3a2222.jpg" /&gt;
&lt;p&gt;The most notable thing about this setup is the party trick: it &lt;em&gt;folds in half&lt;/em&gt;.
This is useful both for travel and for general use.
Sometimes I use it folded just to have more desk or table space in front of me!&lt;/p&gt;
&lt;p&gt;Here it is folded to the smaller configuration.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;iteration4c.1f211f1cd7ecc38e.jpg" /&gt;
&lt;p&gt;To fold it, I lift up the trackball mouse and take out one screw and loosen the other (so I can swing the plate out of the way), then fold it!
This is &lt;em&gt;way&lt;/em&gt; faster than my previous iteration, which required pulling apart a few different hook-and-loop parts and taking off multiple screws.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;iteration4b.04ce94f4309d7446.jpg" /&gt;
&lt;p&gt;It's also more convenient since it fits into my airline personal item bag &lt;em&gt;fully loaded&lt;/em&gt; when folded.
I just take out that one screw, take off the laptop (which fits in another compartment of the same bag), and stow it away with everything on it.
It's made it so I can use my computer in spare moments while traveling.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;iteration4d.2930ff6f2a2f119e.jpg" /&gt;
&lt;p&gt;And this design cuts down on weight a lot.
It's down to just 3.5 pounds (with peripherals, without the laptop) and is pretty comfy to carry around and use.
It's 6.5 pounds if you include my laptop, which is well below the usual post-surgery weight restrictions of not lifting over 10 pounds.
It makes a &lt;em&gt;big&lt;/em&gt; difference for one-handed carrying it around the house!&lt;/p&gt;
&lt;p&gt;Another thing I carried over from a previous iteration was the use of hook-and-loop fasteners.
I have it on the front and back of the laptop holder, and I stick peripherals on there.
It really helps things stay neat and tidy!&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;iteration4e.6ed7d074b83ec4b4.jpg" /&gt;
&lt;p&gt;This setup is not perfect.
I need to reattach the hinges and make sure the screws go in exactly straight, since right now they touch when fully folded and it means the halves spring apart a bit.
This doesn't impact using it folded, but magnets aren't enough to &lt;em&gt;keep&lt;/em&gt; it folded while carrying it.
And it's also not finished, literally: I haven't applied wood finish to it.
But it is a fundamental improvement over even my previous setup, and it has made it &lt;em&gt;dramatically&lt;/em&gt; easier to use my computer in a healthy way.&lt;/p&gt;
&lt;h1 id="my-workshop-a-joy-and-a-privilege"&gt;My workshop: a joy and a privilege&lt;/h1&gt;
&lt;p&gt;I have the privilege of having a nice workshop where I can make things like this.
This takes time, space, and money to establish.
I planned for it—when we were buying a house, we had potential workshop space as one of the criteria.
But even that, buying a house, isn't within reach for everyone.&lt;/p&gt;
&lt;p&gt;It's a joy to have it.
Being able to step out to my garage at any time and just work on physical things instead of the ephemeral abstractions we usually deal in is really wonderful.
And when I have a problem? I can go solve it.&lt;/p&gt;
&lt;p&gt;I've had some sort of tools and workshop for a decade now.
It took me that long, a decade, to get to the point where I was able to make each prototype in a morning.&lt;/p&gt;
&lt;p&gt;If you need something like this laptop tray, reach out to me.
This is life-changing equipment for me, and I want to make sure that people have it if they need it, so I'd make them for close to at-cost.
(The caveat is that &lt;em&gt;this&lt;/em&gt; design only works for laptops with 180 degree hinges, and I'm not sure how I'd update the design for laptops like a Macbook.)&lt;/p&gt;
&lt;h1 id="bonus-my-projector-mount"&gt;Bonus: my projector mount&lt;/h1&gt;
&lt;p&gt;Okay, here's another little thing I built.&lt;/p&gt;
&lt;p&gt;Something else I started doing while sick is working lying on my back, using an ultra short throw projector pointed at the ceiling.
There were times I needed to lie that way to get my heart rate back to normal, or just to avoid &lt;em&gt;any&lt;/em&gt; physical exertion, and being able to use a computer was great!&lt;/p&gt;
&lt;p&gt;I'm doing a lot better now, but still like to use my computer this way sometimes.
However, my projector was just sitting on our bed headboard, and it was easy to bump and move.
So I didn't use it much, because it was inconvenient. (Are you seeing a trend?)&lt;/p&gt;
&lt;p&gt;So, at the end of October, I spent 20 minutes in the workshop and made a projector mount from scrap wood!
I used a few nuts and bolts I had on hand, and another hinge like I had for the keyboard tray.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/evolving-ergo-setup/&amp;#x2F;processed_images&amp;#x2F;projector.15e8203af44adcec.jpg" /&gt;
&lt;p&gt;Now it's rock solid!
It's easy to move the mount around if we need to take it off the bed and put it back, and it stays in a steady angle.
You can adjust the angle pretty easily, too.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;Okay, that's all I've got!
If you have unique homemade ergonomic setups, I'd love to chat with you about them.
And if you want plans to make something like mine, or you'd like me to make you one, feel free to &lt;a href="mailto:me@ntietz.com"&gt;email me&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-engineers"&gt;
&lt;p&gt;I've mostly had software engineers ask me about it, but also some others like a mechanical engineer! &lt;a href="https://ntietz.com/blog/evolving-ergo-setup/#fr-engineers-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-scrap-wood"&gt;
&lt;p&gt;In part, this is so that I can finally turn some of the previous iterations into scrap wood.
They have decent size pieces of plywood that I could use for other things!
But I don't want to recycle them until I document them, so here that is. &lt;a href="https://ntietz.com/blog/evolving-ergo-setup/#fr-scrap-wood-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-except-talon"&gt;
&lt;p&gt;That is, except my voice coding setup with Talon.
That does have its own drawbacks, though, and I was pretty rusty with Talon then.
It's not the easiest to use in public or noisy environments. &lt;a href="https://ntietz.com/blog/evolving-ergo-setup/#fr-except-talon-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-crowdstrike"&gt;
&lt;p&gt;Well, it was this and talk about CrowdStrike.
The morning of my procedure was when that was all going down.
Their department had two working computers; I was lucky the one they needed for me was up.
The IT people were in and out during the procedure to manually fix a bunch of PCs. &lt;a href="https://ntietz.com/blog/evolving-ergo-setup/#fr-crowdstrike-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 02 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/evolving-ergo-setup/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Colt's Revolutions</title><link>https://taylor.town/colt</link><description>hungry for gun funds</description><author>taylor.town</author><pubDate>Mon, 02 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/colt</guid></item><item><title>Run a Bluesky PDS From Home</title><link>https://justingarrison.com/blog/2024-12-02-run-a-bluesky-pds-from-home/</link><description>Take ownership—and responsibility—of your social data</description><author>Justin Garrison's Homepage</author><pubDate>Mon, 02 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://justingarrison.com/blog/2024-12-02-run-a-bluesky-pds-from-home/</guid></item><item><title>/tmp is not a tmpfs on NixOS</title><link>https://blog.erethon.com/log/2024-12-02-nixos-tmp/</link><description>The other day I realized that /tmp/ on my NixOS installations is not a tmpfs as I'm used to from other distros. Instead, NixOS relies on this systemd timer that cleans up old files from /tmp/. This is not NixOS specific, other systemd based distros also run this timer.
Looking at the NixOS boot.tmp related options, we see there's an option (cleanOnBoot) that clears files on boot and is disabled by default.</description><author>Erethon's Corner</author><pubDate>Mon, 02 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.erethon.com/log/2024-12-02-nixos-tmp/</guid></item><item><title>A quieter place</title><link>https://blog.bayindirh.io/blog/a-quieter-place/</link><description>&lt;p&gt;While this blog was silent, my life was buzzing with activity. I have been trying to tend to multiple big projects at work and in my personal life, like a juggler trying not to drop anything.&lt;/p&gt;
&lt;p&gt;Not everything went as planned. The blog got sidelined, personal projects lost momentum, and a couple of them are temporarily frozen. On the other hand, I managed to finish a big migration at work. As always, Murphy learned what we were doing and motivated us to finish faster by intervening at the most convenient time with maximum effect (at least from his perspective). A big pile of important things got handled in my personal and work life, keeping me busy or at the edge, or both.&lt;/p&gt;
&lt;p&gt;Personally, I managed to get my &lt;a href="https://notes.bayindirh.io"&gt;digital garden&lt;/a&gt; to grow, write more Go, and love it in the process. I decluttered my life further in digital and physical realms. Decluttering things take a long time when you're trying to handle decades of clutter collected by a past me who didn't understand the consequences of what he's doing. On the other hand, we didn't have these organizational superpowers back in the day, and I didn't know better, to be honest.&lt;/p&gt;
&lt;p&gt;This decluttering process is not limited to my collected cruft. I'm simplifying my digital life and the places I frequent. I'm not using X (formerly Twitter) and not adding new photos to my Instagram account anymore (since I don't want to provide AI training material to Meta, a subject worthy of its separate blog post). Instead, I moved to &lt;a href="https://mastodon.sdf.org/@bayindirh"&gt;Mastodon&lt;/a&gt; and will use another service to share my photography. Recently, I subscribed to a high-quality local newspaper's digital edition.&lt;/p&gt;
&lt;p&gt;These moves are a new iteration of the trend I started by switching to Kagi in 2023 (which again needs a separate blog post), which profoundly affected my daily life. Using a user-centric search engine without ads has an immediate and measurable effect on life quality since you don't mentally filter ads and other SEO spam with every search, which is mentally draining.&lt;/p&gt;
&lt;p&gt;I felt the same after subscribing to the local newspaper. Higher editorial quality, almost no ads, and access to the paper's print version in PDF form made a profound difference in how I consumed the news and felt afterward (i.e., not mentally exhausted).&lt;/p&gt;
&lt;p&gt;The movement from popular, mainstream sites to well-selected, smaller, yet higher-quality environments made me realize how advertisements, feed algorithms, and optimizations for eyes drain our willpower, energy, and capacity to focus and do meaningful work. It's the same with useless discussions with people one doesn't know and doesn't want to agree on anything.&lt;/p&gt;
&lt;p&gt;From now on, I'll be much more selective about where I frequent, who I engage with, and how I spend my mental energy and focus. I'll build a quieter place because I want to be tired for more meaningful reasons when I go to bed.&lt;/p&gt;
&lt;p&gt;Until next time,&lt;/p&gt;
&lt;p&gt;Be kind.&lt;/p&gt;</description><author>bayindirh</author><pubDate>Mon, 02 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.bayindirh.io/blog/a-quieter-place/</guid></item><item><title>The next platform</title><link>https://www.macchaffee.com/blog/2024/the-next-platform/</link><description>&lt;p&gt;As someone who has built a career on Kubernetes, I'm always thinking about what "the next platform" is (for job security purposes). By "platform", I mean the kind that platform engineers like myself build for internal dev teams on which to run their applications. Kubernetes has been the star platform for quite a few years now (or maybe that's just my internet bubble), but will that last forever?&lt;/p&gt;
&lt;p&gt;Despite the fact that I stan Kubernetes on this blog, I understand more than most that it can be quite painful, so I've been on a search for "the next platform" lately. Let's start with a rough list of problems with Kubernetes-based platforms and a list of requirements for "the next platform".&lt;/p&gt;
&lt;h2 id="problems-with-kubernetes-based-platforms"&gt;Problems with Kubernetes-based platforms&lt;/h2&gt;
&lt;p&gt;You could train a large language model on Kubernetes complaints alone, but I'll just list some common ones:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Steep learning curve.&lt;/li&gt;
&lt;li&gt;Lots of moving parts.&lt;/li&gt;
&lt;li&gt;Requires a dozen other CNCF projects to do useful work.&lt;/li&gt;
&lt;li&gt;Helm templating was a mistake but it's too popular now.&lt;/li&gt;
&lt;li&gt;Everything has to be containerized, which complicates dev environments and slows down CI/CD.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And some of my hot takes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kubernetes workloads are insecure by default. Too many CNCF projects simply don't work with safe &lt;code&gt;securityContext&lt;/code&gt; settings or least-privilege RBAC, and these insecure defaults are footguns for devs.&lt;/li&gt;
&lt;li&gt;Manually tuning CPU/RAM &lt;code&gt;requests&lt;/code&gt; and &lt;code&gt;limits&lt;/code&gt; should go the way of the rotary phone. They should be automatically tuned at runtime.&lt;/li&gt;
&lt;li&gt;Kubernetes' killer feature is self-healing, but too many apps are simply not designed with resilience in mind.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Needless to say, there's a lot of room for improvement. But we have to make sure "the next platform" checks all the boxes.&lt;/p&gt;
&lt;h2 id="list-of-requirements-for-the-next-platform"&gt;List of requirements for "the next platform"&lt;/h2&gt;
&lt;p&gt;This is not an exhaustive list either, but we should probably have things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ability to accept HTTP requests from the Internet.&lt;/li&gt;
&lt;li&gt;Ability to store state somewhere that is highly-available, secure, and backed up.&lt;/li&gt;
&lt;li&gt;Ability to manage app configuration and secrets.&lt;/li&gt;
&lt;li&gt;Ability to collect logs, metrics, and other debugging info from your app.&lt;/li&gt;
&lt;li&gt;Ability to easily deploy new versions of your app.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;From that list, most platform-as-a-service (PaaS) providers such as Heroku already do all those things and have existed for a long time. But why isn't everyone using a PaaS provider? I think there are some other requirements that preclude the use of many current PaaS providers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Affordability at scale. Even small-to-medium companies may be spending over $100k per month on their cloud bill these days. Adding the PaaS markup on top would be too much.&lt;/li&gt;
&lt;li&gt;Ability to run on-premise.&lt;/li&gt;
&lt;li&gt;Custom hardware like GPUs, NVMe drives, HSMs, etc.&lt;/li&gt;
&lt;li&gt;Custom networking like peering with on-premise infrastructure.&lt;/li&gt;
&lt;li&gt;Custom security requirements.&lt;/li&gt;
&lt;li&gt;Edge connectivity.&lt;/li&gt;
&lt;li&gt;Compatibility with legacy apps.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are a CTO who is trying to roll out a "platform" and you have those requirements, then a Kubernetes-based platform may be more compelling because of the extra flexibility. I believe every app that runs on Linux can technically run on Kubernetes (even resorting to &lt;a href="https://kubevirt.io/"&gt;KubeVirt&lt;/a&gt; if necessary), but a PaaS provider could easily say "we don't support that".&lt;/p&gt;
&lt;h2 id="possible-contenders"&gt;Possible contenders&lt;/h2&gt;
&lt;p&gt;With these lists in my mind, I've been exploring the landscape of possible contenders for "the next platform". Like all kinds of future-prediction, this is very subjective and very likely to age poorly, so let's forge ahead regardless.&lt;/p&gt;
&lt;h3 id="paleoops"&gt;PaleoOps&lt;/h3&gt;
&lt;p&gt;This is the idea of running everything on one or two big bare-metal servers, usually from budget providers like Hetzner. Not sure if there's a name for this, so I'm calling it PaleoOps. Similar to how the &lt;a href="https://en.wikipedia.org/wiki/Paleolithic_diet"&gt;paleo diet&lt;/a&gt; developed in response to the rapid expansion of processed foods and their health risks, there's some good and bad reasoning used to justify PaleoOps. Applications got super complex super quick and a whole generation of developers got burned, leading to embracing the simpler infrastructure of the past, which &lt;a href="http://rachelbythebay.com/w/2022/01/27/scale/"&gt;has been successful&lt;/a&gt; in some cases.&lt;/p&gt;
&lt;p&gt;But sometimes uncritically embracing older things results in &lt;a href="https://www.macchaffee.com/blog/2024/you-have-built-a-kubernetes/"&gt;suffering from the same problems&lt;/a&gt; that lead to the rise of the modern replacement in the first place. Notable issues include the increased exposure to hardware failure, the lack of edge support, the security risks of non-segmented applications, and the huge amount of features you'd have to develop yourself. So I don't think this will become "the next platform" but it will always serve a niche where the benefits of a fully automated platform are not needed.&lt;/p&gt;
&lt;h3 id="honorable-mention-erlang-darklang"&gt;Honorable mention: Erlang, Darklang&lt;/h3&gt;
&lt;p&gt;Language-based distributed programming models have always been fascinating to me. &lt;a href="https://vereis.com/posts/disterl_inbox"&gt;Erlang/Elixir and other BEAM languages&lt;/a&gt; were early movers, apparently powering vast distributed systems at &lt;a href="https://en.wikipedia.org/wiki/Erlang_(programming_language)#History"&gt;Ericsson&lt;/a&gt;. There was also a time when I thought &lt;a href="https://en.wikipedia.org/wiki/Akka_(toolkit)"&gt;Akka&lt;/a&gt; and actor-based programming would take over the world. Notably those both gloss over the deployment/DevOps work, sometimes just relying on Docker or Kubernetes (a la &lt;a href="https://github.com/bitwalker/libcluster"&gt;libcluster&lt;/a&gt;). &lt;a href="https://darklang.com/"&gt;Darklang&lt;/a&gt; is another group building something like "the next platform", all from a single programming language.&lt;/p&gt;
&lt;p&gt;But all of these are a bit of a moonshot in my opinion since way too much existing code is written in JavaScript, Python, etc. You could compile apps to run on the BEAM, but you can't simply add OTP semantics to existing apps.&lt;/p&gt;
&lt;h3 id="fly-io"&gt;Fly.io&lt;/h3&gt;
&lt;p&gt;I've never used Fly.io personally, so consider my opinion on this subject to be more uninformed than my usual opinions.&lt;/p&gt;
&lt;p&gt;Fly.io was originally developed as a next-generation PaaS that solved one main deficiency of previous-generation PaaSes: Edge support. Their choice of &lt;a href="https://firecracker-microvm.github.io/"&gt;Firecracker&lt;/a&gt; as the hypervisor means they can spin VMs up and down rapidly and pack lots of VMs on a single physical server, keeping costs low. Using VMs means broad support for existing applications that container-based PaaSes may struggle to match.&lt;/p&gt;
&lt;p&gt;I think Fly.io's reliance on bare-metal servers and VMs is an issue. They recently had to &lt;a href="https://fly.io/blog/machine-migrations/"&gt;re-invent the wheel of VM migrations&lt;/a&gt; with much difficulty. This is something existing hypervisors have had decades to perfect (&lt;a href="https://virtualizationreview.com/articles/2016/09/14/evolution-of-vmware-vmotion.aspx"&gt;vMotion has existed since 2003&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Additionally, you can bet that major cloud providers don't just slide servers into racks and hope they don't break. They almost certainly have fancy methods of capacity planning, redundancy, lifecycle analysis, and failure analysis to reduce the chances your pet VM randomly dies due to a bad memory stick.&lt;/p&gt;
&lt;p&gt;But hardware failure will always be a problem for any application that runs on a single server (unless that server is an &lt;a href="https://arstechnica.com/information-technology/2023/07/the-ibm-mainframe-how-it-runs-and-why-it-survives/"&gt;IBM mainframes with full hardware redundancy&lt;/a&gt;). Because hardware failure is rare and often hidden from users, this can be a sneaky footgun that devs consider too unlikely to matter until it happens. I believe "the next platform" should disarm this footgun.&lt;/p&gt;
&lt;h3 id="serverless-aws-lambda"&gt;Serverless - AWS Lambda&lt;/h3&gt;
&lt;p&gt;Launched in 2014, AWS Lambda allows you to upload a file containing a function that can process HTTP requests. It uses Firecracker to spawn these VMs rapidly and provide secure multi-tenancy. Lambda promised to revolutionize deployment and really popularized the concept of "&lt;a href="https://en.wikipedia.org/wiki/Serverless_computing"&gt;serverless&lt;/a&gt;" apps that do not know or care about the server(s) running the app.&lt;/p&gt;
&lt;p&gt;But I believe Lambda was a bit ahead of its time. Web frameworks like Django were quite popular, but you couldn't just run Django on Lambda without a tool like &lt;a href="https://github.com/zappa/Zappa"&gt;Zappa&lt;/a&gt; to magically make it "serverless", which wouldn't come out for another 2-3 years. You'd also want a serverless database like Aurora Serverless which wouldn't come out &lt;a href="https://aws.amazon.com/blogs/aws/aurora-serverless-ga/"&gt;until 2018&lt;/a&gt; (or &lt;a href="https://aws.amazon.com/blogs/aws/amazon-aurora-postgresql-limitless-database-is-now-generally-available/"&gt;2024 if you use postgres&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Given this is an AWS service, setting up a simple Lambda requires IAM roles, API gateway, and maybe some KMS config which can get quite complicated. There were also problems with cold-start times of &lt;a href="https://aws.amazon.com/blogs/compute/operating-lambda-performance-optimization-part-1/"&gt;100-1000ms&lt;/a&gt; that didn't have great workarounds until later. And perhaps the loudest problem of all was unexpectedly huge bills when Lambdas got DoS'd or &lt;a href="https://news.ycombinator.com/item?id=31907374"&gt;invoked by infinite loops&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;One notable achievement was that &lt;a href="https://adhoc.team/2022/01/18/covidtests-usps-aws-managed-services/"&gt;Lambda was likely used for the successful launch of covidtests.gov in 2022&lt;/a&gt;. This is in stark contrast to the &lt;a href="https://en.wikipedia.org/wiki/HealthCare.gov"&gt;disastrous launch of healthcare.gov in 2013&lt;/a&gt; which used traditional deployment methods and couldn't withstand more than 1,100 of the 250,000 concurrent users trying to access it.&lt;/p&gt;
&lt;p&gt;Overall, I think of Lambda as the beta release of "the next platform". It has proven some core ideas like serverless computing are possible, but it has all the rough edges of an early mover.&lt;/p&gt;
&lt;h3 id="v8-isolates"&gt;V8 Isolates&lt;/h3&gt;
&lt;p&gt;I've never been a big fan of Cloudflare's market dominance, but I must say that the more I read about &lt;a href="https://developers.cloudflare.com/workers/platform/storage-options/"&gt;Cloudflare's serverless products&lt;/a&gt;, the more I start shaking in my platform engineering boots. I also haven't used any of these products, so perhaps they sound better than they really are. &lt;a href="https://deno.com/deploy"&gt;Deno Deploy&lt;/a&gt; is also built on V8 isolates and serves as a compelling competitor to Cloudflare, but I know even less about their offerings.&lt;/p&gt;
&lt;p&gt;The core product is Cloudflare Workers, which is their version of AWS Lambda. The key difference is they use &lt;a href="https://developers.cloudflare.com/workers/reference/how-workers-works/#isolates"&gt;V8 isolates&lt;/a&gt; instead of Firecracker VMs, which are essentially using the same tech that isolates Chrome tabs from each other (apparently Chrome uses separate processes &lt;em&gt;and&lt;/em&gt; isolates, but Cloudflare runs all the isolates in the same process, which &lt;a href="https://blog.cloudflare.com/mitigating-spectre-and-other-security-threats-the-cloudflare-workers-security-model/"&gt;they claim is still safe&lt;/a&gt;). This eliminates the cold-start problem and drastically reduces the overhead for running each isolate. V8 isolates primarily support JavaScript (including some JS web frameworks), but they also support Rust and Python &lt;a href="https://developers.cloudflare.com/workers/languages/python/how-python-workers-work/"&gt;by compiling to WebAssembly&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Things get even more interesting when you look at all the &lt;a href="https://developers.cloudflare.com/workers/platform/storage-options/"&gt;other compatible offerings&lt;/a&gt;, like Workers KV (distributed key-value store), R2 (S3-like object storage), D1 (managed relational databases), and even &lt;a href="https://developers.cloudflare.com/workers/ci-cd/"&gt;CI/CD&lt;/a&gt;. Deno has also been rolling out a similarly impressive list of compatible products. These all seem like compelling replacements for many common bits of infrastructure.&lt;/p&gt;
&lt;p&gt;But the devil is in the details. Workers have some quite-restrictive &lt;a href="https://developers.cloudflare.com/workers/platform/limits/"&gt;limits&lt;/a&gt; like a max 30 seconds of CPU time, a max 128MB of memory, and just barely enough POSIX support to be usable-but-painful. They haven't really solved the problem of huge bills from runaway Workers either. And while you are fantasizing about the long list of complex infra you could replace with these products, you are like a fish being lured in by an angler fish's glow, only to be swallowed whole by the jaws of vendor lock-in.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Despite all the downsides, I'm starting to feel like we're getting close to "the next platform". Perhaps Workers is like a 1.0 release of something that only really gets good at version 2.0. Maybe V8 isolates will make it to 2.0, or maybe they'll be replaced by unikernels (like &lt;a href="https://nanovms.com/"&gt;NanoVMs&lt;/a&gt;) or &lt;a href="https://opensource.microsoft.com/blog/2024/11/07/introducing-hyperlight-virtual-machine-based-security-for-functions-at-scale/"&gt;this Hyperlight thing that Microsoft is making&lt;/a&gt;. To me, the last remaining pieces of the "next platform" puzzle are things like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Radically solving the pricing problem, where the cost of using the platform is just a slight markup on EC2 prices.&lt;/li&gt;
&lt;li&gt;Fully open-source, including the ability to deploy it yourself (like Kubernetes).&lt;/li&gt;
&lt;li&gt;Strikes the right balance between "compatible with existing apps" and "stopping people from deploying the same bloated/unreliable/insecure garbage we've been building for decades".&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At this point, we have one leg in the past and one leg in the future. The past is pet servers and VMs. The present is clusters of containers that still have many of the same pain points. When the future comes, I just hope it's not another chore.&lt;/p&gt;</description><author>Mac's Tech Blog</author><pubDate>Mon, 02 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.macchaffee.com/blog/2024/the-next-platform/</guid></item><item><title>Advent of Code 2024</title><link>https://mfashby.net/posts/2024-12-01-aoc2024/</link><description/><author>Home on mfashby.net</author><pubDate>Mon, 02 Dec 2024 00:11:35 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2024-12-01-aoc2024/</guid></item><item><title>2024.11.DisappearingMoment</title><link>https://newsletter.disappearingmoment.com/archive/202411disappearingmoment/</link><description>&lt;p&gt;If the world is ending, I want to be surrounded by kittens when it happens.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="10-week-old kitten littermates asleep on top of each other" draggable="false" src="https://assets.buttondown.email/images/8b100aba-a61a-404f-a94c-00cba43d8b4c.JPG" /&gt;&lt;figcaption&gt;Sleeping Marty, JoJo, and Lloyd (top to bottom)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;figure&gt;&lt;img alt="10-week-old kitten littermates waking up and looking at the camera" draggable="false" src="https://assets.buttondown.email/images/3bdd47c7-5740-4092-ae38-75025b8a6dbc.jpg?w=960&amp;amp;fit=max" /&gt;&lt;figcaption&gt;Sleepy Marty, JoJo, and Lloyd (top to bottom)&lt;/figcaption&gt;&lt;/figure&gt;
&lt;p&gt;Welcome to November 2024’s Disappearing Moment, an inventory of my experiences. I hope you enjoy it.&lt;/p&gt;
&lt;h2&gt;Podcasts&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.iheart.com/podcast/105-behind-the-bastards-29236323/" target="_blank"&gt;Behind the Bastards&lt;/a&gt;: RFK, Jr. (Worth My Time): Grating hosts tell the tale of a degenerate boor.&lt;/p&gt;
&lt;h2&gt;Nerdy Software&lt;/h2&gt;
&lt;p&gt;I programmed my first beats at a Rock and Roll Hall of Fame exhibit. Now I get my kicks with &lt;a href="https://www.onemotion.com/drum-machine/" target="_blank"&gt;Drum Machine&lt;/a&gt; by Tomas Eriksson.&lt;/p&gt;
&lt;h2&gt;Free Font&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://rsms.me/inter/" target="_blank"&gt;Inter&lt;/a&gt; is popular and deserves to be. Thanks, &lt;a href="https://rsms.me/about/" target="_blank"&gt;Rasmus Anderson&lt;/a&gt;, for creating an attractive, flexible, open source font.&lt;/p&gt;
&lt;h2&gt;Bougie Products&lt;/h2&gt;
&lt;p&gt;Beth has small hands; mine are large. She’s left-handed; I’m wrong-handed. We have the same favorite pen: &lt;a href="https://www.unibrands.co/products/roller-rollerball-pens?variant=39369583821006" target="_blank"&gt;Uniball Roller, blue, .5 mm&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Personal Finance and Investing&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.parnassus.com/parnassus-mutual-funds" target="_blank"&gt;Parnassus Funds&lt;/a&gt; check my boxes. Beth and I use them for the managed, ESG portion of our retirement investments.&lt;/p&gt;
&lt;h2&gt;Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Rachel Donald, “&lt;a href="https://www.planetcritical.com/p/cyber-security-experts-warn-election-hacked" target="_blank"&gt;Cyber-Security Experts Warn Election Was Hacked&lt;/a&gt;” and “&lt;a href="https://www.planetcritical.com/p/election-fraud-debunked" target="_blank"&gt;Election Fraud Debunked?&lt;/a&gt;” (I Liked It): I have more empathy for QAon impulses, and less for anyone with a Fox taint. Admitting you’re wrong is table stakes for redemption.&lt;/li&gt;
&lt;li&gt;Maurice Mitchell, “&lt;a href="https://forgeorganizing.org/article/building-resilient-organizations" target="_blank"&gt;Building Resilient Organizations&lt;/a&gt;” (I Liked It): Admitting you’re wrong is table stakes for redemption. So is giving a fuck about not acting like a bigot.&lt;/li&gt;
&lt;li&gt;Maria Popova, “&lt;a href="https://www.themarginalian.org/2024/10/22/marginalian-18/" target="_blank"&gt;18 Life-Learnings from 18 Years of &lt;em&gt;The Marginalian&lt;/em&gt;&lt;/a&gt;” (I Liked It): More vulnerable, less cerebral. We remember our feelings better than we remember our thoughts.&lt;/li&gt;
&lt;li&gt;Sara Slattery and Molly Huddle, &lt;a href="https://howshediditbook.com/" target="_blank"&gt;&lt;em&gt;How She Did It&lt;/em&gt;&lt;/a&gt; (2022) (I Liked It): Elite women’s running athletes tell their stories. It’s oral history lite, a solid addition to the canon.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;I Do Not Use&lt;/h2&gt;
&lt;p&gt;The are services that I avoid. Anything in bold is &lt;a href="https://en.wikipedia.org/wiki/Considered_harmful" target="_blank"&gt;&lt;strong&gt;considered harmful&lt;/strong&gt;&lt;/a&gt;. I keep this list updated on &lt;a href="https://disappearingmoment.com/uses/" target="_blank"&gt;the Uses page on my website&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adobe, except Acrobat for iPadOS, the most accessible PDF reader I’ve found for iPads.&lt;/li&gt;
&lt;li&gt;AT&amp;amp;T&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brave&lt;/strong&gt; “runs cryptocurrency, &lt;a href="https://brave.com/rewards-update/" target="_blank"&gt;has held payments to creators without disclosing that creators couldn’t receive rewards&lt;/a&gt;, has made dangerously misleading claims about fingerprinting resistance, is run by a CEO who &lt;a href="https://arstechnica.com/information-technology/2014/03/new-mozilla-ceo-issues-statement-expresses-sorrow-for-causing-pain/" target="_blank"&gt;spent thousands of dollars opposing gay marriage&lt;/a&gt;, and &lt;a href="https://www.pcmag.com/news/brave-browser-caught-redirecting-users-through-affiliate-links" target="_blank"&gt;has rewritten typed URLs with affiliate links&lt;/a&gt;.” (Rohan Kumar’s entry on Brave Search in “&lt;a href="https://seirdy.one/posts/2021/03/10/search-engines-with-own-indexes/" target="_blank"&gt;A look at search engines with their own indexes&lt;/a&gt;”)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ChatGPT, Claude, Cohere&lt;/strong&gt;, etc. I’m fine with open source Large Language Models.&lt;/li&gt;
&lt;li&gt;Constant Contact&lt;/li&gt;
&lt;li&gt;Discord&lt;/li&gt;
&lt;li&gt;DraftKings&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lemmy.ml/post/31321" target="_blank"&gt;&lt;strong&gt;DuckDuckGo performs privacy theater&lt;/strong&gt;&lt;/a&gt;. Then &lt;a href="https://www.bleepingcomputer.com/news/security/duckduckgo-browser-allows-microsoft-trackers-due-to-search-agreement/" target="_blank"&gt;it signs hostile agreements and tries to gaslight its users&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="http://thecostofknowledge.com/" target="_blank"&gt;&lt;strong&gt;Elsevier&lt;/strong&gt;&lt;/a&gt;’s “economic model was inequitable and non-transparent” (&lt;a href="https://libraries.mit.edu/scholarly/publishing/mit-elsevier/" target="_blank"&gt;MIT Libraries&lt;/a&gt;). In other words, Elsevier is “&lt;a href="https://www.theguardian.com/science/2023/may/07/too-greedy-mass-walkout-at-global-science-journal-over-unethical-fees" target="_blank"&gt;too greedy&lt;/a&gt;”.&lt;/li&gt;
&lt;li&gt;Evite&lt;/li&gt;
&lt;li&gt;FanDuel&lt;/li&gt;
&lt;li&gt;GoDaddy&lt;/li&gt;
&lt;li&gt;&lt;a href="https://about.google/products/" target="_blank"&gt;Google&lt;/a&gt; is &lt;a href="https://killedbygoogle.com/" target="_blank"&gt;capricious and untrustworthy&lt;/a&gt;. I look forward to migrating my workplace away from &lt;a href="https://workspace.google.com/" target="_blank"&gt;Google Workspace&lt;/a&gt;. I use &lt;a href="https://invidious.io/" target="_blank"&gt;Invidious&lt;/a&gt; when I can’t avoid YouTube.&lt;/li&gt;
&lt;li&gt;Hoopla&lt;/li&gt;
&lt;li&gt;Intuit (Credit Karma, Mailchimp, TurbTax). I can live with QuickBooks Online for nonprofits.&lt;/li&gt;
&lt;li&gt;JanitorAI&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.osnews.com/story/139270/do-not-use-kagi/" target="_blank"&gt;&lt;strong&gt;Kagi&lt;/strong&gt;&lt;/a&gt; “seems happy to &lt;a href="https://kagifeedback.org/d/2808-reconsider-your-partnership-with-brave" target="_blank"&gt;use Brave’s commercial API&lt;/a&gt; (allowing blatant homophobia in the comments) and &lt;a href="https://kagifeedback.org/d/865-suicide-results-should-probably-have-a-dont-do-that-widget-like-google/50" target="_blank"&gt;allow its results to recommend suicide methods without intervention&lt;/a&gt;.” (Rohan Kumar’s entry on Brave Search in “&lt;a href="https://seirdy.one/posts/2021/03/10/search-engines-with-own-indexes/" target="_blank"&gt;A look at search engines with their own indexes&lt;/a&gt;”)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LastPass&lt;/strong&gt; is clunky and &lt;a href="https://en.wikipedia.org/wiki/LastPass#Security_incidents" target="_blank"&gt;untrustworthy&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;LinkedIn&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Live_Nation_Entertainment#Legal_issues" target="_blank"&gt;Live Nation&lt;/a&gt; puts boring performers in impersonal venues. I wouldn’t go to these shows if they were free.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Meta&lt;/strong&gt; (Facebook, Instagram, Messenger, Threads, WhatsApp) is:&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Facebook%E2%80%93Cambridge_Analytica_data_scandal" target="_blank"&gt;Anxiety and Depression&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Facebook%E2%80%93Cambridge_Analytica_data_scandal" target="_blank"&gt;Sedition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Criticism_of_Facebook" target="_blank"&gt;Self-Destruction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.politico.com/news/2021/10/25/facebook-moderate-posts-violent-countries-517050" target="_blank"&gt;Violence&lt;/a&gt; (e.g., &lt;a href="https://www.amnesty.org.uk/press-releases/ethiopia-facebook-algorithms-contributed-human-rights-abuses-against-tigrayans" target="_blank"&gt;Ethiopia&lt;/a&gt;, &lt;a href="https://slate.com/technology/2021/10/facebook-papers-india-modi-misinformation-rss-bjp.html" target="_blank"&gt;India&lt;/a&gt;, and &lt;a href="https://www.amnesty.org/en/latest/news/2022/09/myanmar-facebooks-systems-promoted-violence-against-rohingya-meta-owes-reparations-new-report/" target="_blank"&gt;&lt;s&gt;Myanmar&lt;/s&gt;&lt;/a&gt;). &lt;a href="https://www.buzzfeednews.com/article/alexkantrowitz/facebook-violence" target="_blank"&gt;Happy Birthday&lt;/a&gt;!!!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.vice.com/en/article/nextdoor-knows-its-users-are-racist/" target="_blank"&gt;&lt;strong&gt;Nextdoor Knows Its Users Are Racist&lt;/strong&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Outlook can make Google products seem acceptable.&lt;/li&gt;
&lt;li&gt;Pinterest&lt;/li&gt;
&lt;li&gt;Quora&lt;/li&gt;
&lt;li&gt;Robinhood&lt;/li&gt;
&lt;li&gt;“Slack: At least it’s not Teams!”&lt;/li&gt;
&lt;li&gt;SnapChat&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Criticism_of_Spotify" target="_blank"&gt;&lt;strong&gt;Spotify is an amoral dumpster fire&lt;/strong&gt;&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Strava. I run to escape social media’s impositions.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cupofcoffee.beehiiv.com/p/cup-coffee-september-5-2024" target="_blank"&gt;&lt;strong&gt;Substack conspires with Nazis&lt;/strong&gt;&lt;/a&gt;. See also, why &lt;a href="https://www.citationneeded.news/citation-needed-has-a-new-home/" target="_blank"&gt;Molly White&lt;/a&gt; and &lt;a href="https://www.platformer.news/why-platformer-is-leaving-substack/" target="_blank"&gt;Casey Newton&lt;/a&gt; left Substack.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.flickr.com/photos/deserontoarchives/2695995305/" target="_blank"&gt;Teams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Telegram&lt;/strong&gt; has iffy &lt;a href="https://blog.cryptographyengineering.com/2024/08/25/telegram-is-not-really-an-encrypted-messaging-app/" target="_blank"&gt;security&lt;/a&gt;, &lt;a href="https://techcrunch.com/2024/03/25/telegrams-peer-to-peer-sms-login-service-is-a-privacy-nightmare/" target="_blank"&gt;privacy&lt;/a&gt;, and &lt;a href="https://techcrunch.com/2024/05/24/signals-meredith-whittaker-on-the-telegram-security-clash-and-the-edge-lords-at-openai/" target="_blank"&gt;veracity&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Temu&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TikTok&lt;/strong&gt; and all other services that are based in China.&lt;/li&gt;
&lt;li&gt;Uber. Lyft meets my needs. I’ll use a taxi, if necessary.&lt;/li&gt;
&lt;li&gt;Vanguard has become an embarrassment. Stick to the mutual funds started before 1996 (the year Bogle resigned).&lt;/li&gt;
&lt;li&gt;Webex is worse than Teams.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.bbc.com/news/articles/c74lgwkrmrpo" target="_blank"&gt;&lt;strong&gt;X has no redeeming qualities&lt;/strong&gt;&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/w/index.php?title=Twitter_under_Elon_Musk&amp;amp;section=2#Increase_in_hate_speech" target="_blank"&gt;continues to get worse&lt;/a&gt;. “If your business case for Trump media was, ‘Truth Social is the place for tens of millions of Donald Trump fans to get their social media,’ then the fact that Donald Trump is back on X — and that &lt;a href="https://www.listennotes.com/podcasts/money-stuff-the/fruitcakes-and-nutcases-etf-hLQ6DiRrbOP/?t=1406" target="_blank"&gt;X is clearly the right wing social network now&lt;/a&gt; — undermines that business case.” (&lt;a href="https://www.bloomberg.com/news/audio/2024-09-06/money-stuff-podcast-fruitcakes-and-nutcases-podcast" target="_blank"&gt;Money Stuff: The Podcast&lt;/a&gt;, 2024-09-06)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Yandex&lt;/strong&gt; and all other services that are based in Russia&lt;/li&gt;
&lt;li&gt;Zelle. Venmo meets my needs. I’ll use PayPal, if necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you for spending a few moments with me. I appreciate you and look forward to corresponding again next month.&lt;/p&gt;
&lt;p&gt;Brett&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want to discuss any of the topics in this newsletter or anything else with other Disappearing Moment readers? Please sign up for&lt;/em&gt; &lt;a href="https://august.disappearingmoment.com/" target="_blank"&gt;&lt;em&gt;Perpetual August&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. I think it might be fun.&lt;/em&gt;&lt;/p&gt;</description><author>Disappearing Moment</author><pubDate>Sun, 01 Dec 2024 21:02:02 GMT</pubDate><guid isPermaLink="true">https://newsletter.disappearingmoment.com/archive/202411disappearingmoment/</guid></item><item><title>Lessons from a year with Beeminder</title><link>https://utf9k.net/blog/lessons-beeminder-year-one/</link><description>Apparently it's been slightly more than a year</description><author>utf9k</author><pubDate>Sun, 01 Dec 2024 08:58:00 GMT</pubDate><guid isPermaLink="true">https://utf9k.net/blog/lessons-beeminder-year-one/</guid></item><item><title>Advent of Code 2024: Getting Started</title><link>https://gavinhoward.com/2024/11/advent-of-code-2024-getting-started/</link><description>I don't like Advent of Code, but this year is different. It will be my first ever.</description><author>Gavin D. Howard</author><pubDate>Sun, 01 Dec 2024 03:28:38 GMT</pubDate><guid isPermaLink="true">https://gavinhoward.com/2024/11/advent-of-code-2024-getting-started/</guid></item><item><title>Update for December, 'here we go again' 2024</title><link>https://ciesie.com/post/update_01122024/</link><description>&lt;p&gt;It&amp;rsquo;s been a while since I have written one of these. There&amp;rsquo;s definitely a cadence to these.
That cadence would be something between once and thrice a year. 2024 will have to make with
just one.&lt;/p&gt;
&lt;p&gt;I have spent a bit of time understanding the &lt;a href="https://github.com/ggerganov/ggwave"&gt;ggwave&lt;/a&gt;
project, which allows for sending data over sound. It&amp;rsquo;s not an extremely complicated protocol.
I think the most difficult part to understand is the Reed-Solomon error correction, which is still
something I have not delved deep into. I have first started by understanding Fourier Transform
well. This started with a Jupyter notebook, but I have quickly moved to using
&lt;a href="https://github.com/marimo-team/marimo"&gt;Marimo&lt;/a&gt;. You can find
&lt;a href="https://github.com/MrOneTwo/jupyter_experiments"&gt;my repo here&lt;/a&gt;, but don&amp;rsquo;t expect it to be
polished and guests friendly.&lt;/p&gt;</description><author>ciesie.com</author><pubDate>Sun, 01 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ciesie.com/post/update_01122024/</guid></item><item><title>Access Google Gemini LLM via OpenAI Python Library</title><link>https://saeedesmaili.com/notes/access-google-gemini-llm-via-openai-python-library/</link><description>&lt;p&gt;Google Gemini now can be accessed via OpenAI python library:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-python"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;from&lt;/span&gt; openai &lt;span style="color: #f92672;"&gt;import&lt;/span&gt; OpenAI
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;client &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; OpenAI(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    api_key&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;"GEMINI_API_KEY"&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    base_url&lt;span style="color: #f92672;"&gt;=&lt;/span&gt;&lt;span style="color: #e6db74;"&gt;"https://generativelanguage.googleapis.com/v1beta/openai/"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;## rest of the code as you would use openai&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It support basic text generation, image input, function calling, structured output, and embeddings.
More info and code examples can be found on &lt;a href="https://ai.google.dev/gemini-api/docs/openai" target="_blank"&gt;Gemini docs&lt;/a&gt;
.&lt;/p&gt;</description><author>Saeed Esmaili</author><pubDate>Sun, 01 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://saeedesmaili.com/notes/access-google-gemini-llm-via-openai-python-library/</guid></item><item><title>TLS certificate setup on NixOS</title><link>https://paperless.blog/tls-certificate-setup-on-nixos</link><description>Info dump on how to set up a HTTPS-enabled service on NixOS.</description><author>Paperless</author><pubDate>Sun, 01 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://paperless.blog/tls-certificate-setup-on-nixos</guid></item><item><title>2024 in Review</title><link>https://jasoneckert.github.io/myblog/2024-in-review/</link><description>&lt;p&gt;It&amp;rsquo;s important to pause and reflect every so often to stay grounded and focused. So, each December I look back on the year&amp;rsquo;s events and summarize them in a blog post. And after doing this again this year, I realize that 2024 was one of reflection, gratitude, and exploration.&lt;/p&gt;
&lt;p&gt;I was fortunate enough to go on some epic winter hiking adventures with my dog Pepper in January before she &lt;a href="../remembering-pepper/"&gt;passed away in February&lt;/a&gt; at the age of 13. Losing a dog is the hardest thing because we form such strong bonds, but she was an amazing dog that had an awesome life and I&amp;rsquo;m grateful for all of it. I&amp;rsquo;m reminded of a pet memorial plaque I saw many years ago that said &amp;ldquo;&lt;em&gt;&lt;strong&gt;Don&amp;rsquo;t cry because it&amp;rsquo;s over. Smile because it happened.&lt;/strong&gt;&lt;/em&gt;&amp;rdquo; I spread her ashes in the areas she loved most: my back yard, the sports park behind my house, the public park at the end of my street, and in the stream under the bridge my daughter and I often took her to on the Mill Run trail.&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Sun, 01 Dec 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/2024-in-review/</guid></item><item><title>Your View's Lifetime Is Not Yours</title><link>https://shadowfacts.net/2024/swiftui-lifecycle/</link><description>&lt;p&gt;When SwiftUI was announced in 2019 (oh boy, more than 5 years ago), one of the big things that the Apple engineers emphasized was that a SwiftUI &lt;code&gt;View&lt;/code&gt; is not like a UIKit/AppKit view. As Apple was at pains to note, SwiftUI may evaluate the &lt;code&gt;body&lt;/code&gt; property of a &lt;code&gt;View&lt;/code&gt; arbitrarily often. It’s easy to miss an important consequence this has, though: your &lt;code&gt;View&lt;/code&gt; will also be initialized arbitrarily often. This is why SwiftUI views are supposed to be structs which are simple value types and can be stack-allocated: constructing a &lt;code&gt;View&lt;/code&gt; needs to be cheap.&lt;/p&gt;
&lt;p&gt;More precisely, what I mean by the title of this post is that the lifetime of a struct that conforms to &lt;code&gt;View&lt;/code&gt; is unmoored from that of the conceptual thing representing a piece of your user interface.&lt;/p&gt;
&lt;!-- excerpt-end --&gt;
&lt;aside class="inline"&gt;
&lt;p&gt;A note on definitions: in this post, I’m using the word ‘lifecycle’ and ‘lifetime’ in very particular ways. Lifecycle is connected to the conceptual view, and encompasses things like the &lt;code&gt;onAppear&lt;/code&gt;/&lt;code&gt;onDisappear&lt;/code&gt; modifiers. Lifetime, however, is a property of a value: the time from its initialization to its destruction (that is, how long the value exists in memory).&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;This comes up on social media with some regularity. Every few months there’ll be questions about (either directly, or in the form of questions that boil down to) why some view’s initializer is being called more than expected. One particularly common case is people migrating from Combine/&lt;code&gt;ObservableObject&lt;/code&gt; to the iOS 17+ &lt;code&gt;@Observable&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you were not paying attention to SwiftUI in the early days, or have only started learning it more recently, there are some important details about the nature of the framework you might have missed. One of the things the SwiftUI engineers emphasized when it was announced was that the &lt;code&gt;body&lt;/code&gt; property can be called at any time by the framework and may be called as often as the framework likes. What follows from this—that’s not entirely obvious or always explicitly stated—is that &lt;code&gt;View&lt;/code&gt; initializers run with the same frequency. Consider the minimal possible case:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-kw"&gt;struct &lt;/span&gt;MyParentView: &lt;span class="hl-ty"&gt;View &lt;/span&gt;{&lt;span class="hl-kw"&gt;
    var &lt;/span&gt;body: &lt;span class="hl-kw"&gt;some &lt;/span&gt;&lt;span class="hl-ty"&gt;View &lt;/span&gt;{&lt;span class="hl-prop"&gt;
        MyChildView&lt;/span&gt;()
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Evaluating &lt;code&gt;MyParentView&lt;/code&gt;’s body necessarily means running the initializer for &lt;code&gt;MyChildView&lt;/code&gt;. SwiftUI does a lot of work to make things feel magical, but, at the end of the day, the code that you’re writing is ultimately actually running. So, the initializer runs.&lt;/p&gt;
&lt;p&gt;After that happens, SwiftUI can compare the new and old values of &lt;code&gt;MyChildView&lt;/code&gt; to decide whether it’s changed and, in turn, whether to read its body. But, by the time the framework is making that decision, the initializer has already been run.&lt;/p&gt;
&lt;h2&gt;An Autoclosure Corollary&lt;/h2&gt;
&lt;p&gt;It follows, I argue, from the reasons given above that it is a poor API design choice for SwiftUI property wrappers to use &lt;code&gt;@autoclosure&lt;/code&gt; parameters in the wrapped value initializer.&lt;/p&gt;
&lt;p&gt;An entirely reasonable principle of API design is that you should hide unnecessary complexity from consumers of your API. There is, however, a very fine line between hiding unnecessary complexity and obscuring necessary complexity—or worse, being actively misleading about what’s happening. I think &lt;code&gt;@StateObject&lt;/code&gt; and its autoclosure initializer tread too close to that line.&lt;/p&gt;
&lt;p&gt;The use of an autoclosure hides the fact that the property wrapper itself is getting initialized frequently—just like the view that contains it (initializing the view necessarily means initializing all of its properties—i.e., the property wrappers themselves). If your API design is such that you can very easily write two pieces code that, on the surface, look almost identical but have dramatically different performance characteristics, then it hinders (especially inexperienced) developers’ understanding.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="hl-attr"&gt;@&lt;span class="hl-ty"&gt;StateObject &lt;/span&gt;&lt;span class="hl-rst"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="hl-kw"&gt;var &lt;/span&gt;model = &lt;span class="hl-ty"&gt;MyViewModel&lt;/span&gt;()&lt;span class="hl-attr"&gt;
&lt;span class="hl-cmt"&gt;// vs.&lt;/span&gt;
@&lt;span class="hl-ty"&gt;State &lt;/span&gt;&lt;span class="hl-rst"&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="hl-kw"&gt;var &lt;/span&gt;model = &lt;span class="hl-ty"&gt;MyViewModel&lt;/span&gt;()
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That the name &lt;code&gt;StateObject&lt;/code&gt; conflates the behavior with &lt;code&gt;State&lt;/code&gt; certainly doesn’t help either.&lt;/p&gt;
&lt;h2&gt;Some Recommendations&lt;/h2&gt;
&lt;p&gt;Not realizing that view initializers—not just the &lt;code&gt;body&lt;/code&gt; property—are in the hot path can quickly get you into hot water, particularly if you’re doing any expensive work or are trying to use RAII&lt;sup class="footnote-reference"&gt;&lt;a href="#1"&gt;1&lt;/a&gt;&lt;/sup&gt; with a value in a view property. So, what follows are some general recommendations for avoiding those issues that are as close to universally correct as I’m willing to claim in writing.&lt;/p&gt;
&lt;h3&gt;No Expensive Work in Inits&lt;/h3&gt;
&lt;p&gt;This is a simple one. As discussed earlier, this is equivalent to doing that same expensive work in a view’s &lt;code&gt;body&lt;/code&gt; property. But SwiftUI calls both of those at arbitrary times with arbitrary frequency. Doing expensive work in either place will quickly cause performance issues.&lt;/p&gt;
&lt;h3&gt;Ignore &lt;code&gt;View&lt;/code&gt; Lifetimes&lt;/h3&gt;
&lt;p&gt;Even more generally, you should ignore the lifetime of your &lt;code&gt;View&lt;/code&gt; structs altogether. “When is my view initialized? Who knows, I don’t care!” Even if, at some point during development, you find that a particular view’s initializer is called at a useful time, avoid depending on that, even if the work you need to do is cheap. While, right now, you might have a good handle on a particular &lt;code&gt;View&lt;/code&gt;’s lifetime, it’s very easy to inadvertently change that in the future, either by introducing additional dependencies in the view’s parent, due to changes to the &lt;a href="https://developer.apple.com/videos/play/wwdc2021/10022/"&gt;view’s identity&lt;/a&gt;, or due to entirely framework-internal changes.&lt;/p&gt;
&lt;p&gt;Note that this is a particularly important idea and is worth hammering home: a &lt;code&gt;View&lt;/code&gt;’s initializer is called as part of its &lt;em&gt;parent’s&lt;/em&gt; &lt;code&gt;body&lt;/code&gt;. This is particularly prone to action at a distance: seemingly-unrelated changes at the callsite where a view is used (or beyond) can dramatically change how often that view is initialized.&lt;/p&gt;
&lt;h3&gt;Use Lifecycle Modifiers&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://developer.apple.com/documentation/swiftui/state#Store-observable-objects"&gt;&lt;code&gt;@State&lt;/code&gt; docs&lt;/a&gt; call this out with regard to &lt;code&gt;Observable&lt;/code&gt;, but it’s true in general. Rather than putting expensive objects as the wrapped value of a &lt;code&gt;@State&lt;/code&gt; property wrapper, use SwiftUI’s lifecycle modifiers like &lt;code&gt;.onAppear&lt;/code&gt; or &lt;code&gt;.task&lt;/code&gt; to construct them.&lt;/p&gt;
&lt;p&gt;What’s more, if you need to do any one-time setup work—even inexpensive—related to a particular instance of a &lt;em&gt;conceptual&lt;/em&gt; view, the lifecycle modifiers are the way to go. They abstract you away from the particularities of the lifetime of the struct instance itself.&lt;/p&gt;
&lt;div class="footnote-definition" id="1"&gt;&lt;span class="footnote-definition-label"&gt;1. &lt;/span&gt;
&lt;p&gt;Resource Acquisition Is Initialization: the idea that the lifetime of a value is tied to some other resource (e.g., an open file). The wrapped value of a &lt;code&gt;@State&lt;/code&gt; property is evaluated every time the containing view is initialized, so doing any resource acquisition work therein can be expensive.&lt;/p&gt;
&lt;/div&gt;</description><author>Shadowfacts</author><pubDate>Sun, 01 Dec 2024 01:41:42 GMT</pubDate><guid isPermaLink="true">https://shadowfacts.net/2024/swiftui-lifecycle/</guid></item><item><title>Modern Portfolio Theory I: Random portfolio coverage in log volatility—return space</title><link>https://bytepawn.com/modern-portfolio-theory-random-portfolio-coverage-in-log-volatility-return-space.html</link><description>&lt;p&gt;I examine the coverage of random portfolios in log volatility—return space using Monte Carlo methods with different randomization techniques for the 2023 daily closing prices of the 100 stocks constituting the Nasdaq-100.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Monte Carlo casino" src="/images/logvolatility-5.png" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 01 Dec 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/modern-portfolio-theory-random-portfolio-coverage-in-log-volatility-return-space.html</guid></item><item><title>Singularity short story fika, co-writing, and Upplandsmuseet</title><link>https://liza.io/singularity-short-story-fika-co-writing-and-upplandsmuseet/</link><description>&lt;p&gt;This was my first &amp;ldquo;proper&amp;rdquo; weekend in the new place. Yesterday morning I met with a friend from my writing group at &lt;a href="https://himlenarblasomenapelsin.com/"&gt;Himlen Är Blå Som En Apelsin&lt;/a&gt; - a really cozy cafe that is a two minute walk from my apartment.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Sat, 30 Nov 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/singularity-short-story-fika-co-writing-and-upplandsmuseet/</guid></item><item><title>[TIL] nix flake update: Updating dependencies of a Nix flake</title><link>https://davi.sh/til/nix/flake-update/</link><description>&lt;p&gt;I just realized today that I haven’t updated VSCode on my Mac in almost a year. It turns
out that if you install any program through Nix, its version is determined by the
version of nixpkgs in your &lt;code&gt;flake.lock&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;To update nixpkgs and get newer versions of all your programs, &lt;code&gt;cd&lt;/code&gt; into the directory
with your flake and run &lt;code&gt;nix flake update&lt;/code&gt;. It’s the &lt;code&gt;pacman -Syu&lt;/code&gt; of nix!&lt;/p&gt;</description><author>Davis Haupt's Blog</author><pubDate>Sat, 30 Nov 2024 19:00:00 GMT</pubDate><guid isPermaLink="true">https://davi.sh/til/nix/flake-update/</guid></item><item><title>Concurrency diagrams</title><link>https://philbooth.me/blog/concurrency-diagrams/</link><description>&lt;p&gt;When engineers discuss program design and system architecture,
a common source of misunderstanding is concurrency.
Often that&amp;rsquo;s because we make internal assumptions about it,
which we presume to be self-evident.
But we don&amp;rsquo;t all make the same assumptions,
so you can end up in a situation
where multiple conflicting beliefs are held
about the concurrency of a system
and nobody realises.
Left unchecked,
these misunderstandings can lurk
until much later in the development process,
when they&amp;rsquo;re more expensive to fix.
You can prevent these misunderstandings from happening
by making concurrency explicit up-front,
in a diagram.&lt;/p&gt;
&lt;p&gt;Concurrency diagrams are very simple
and usually quick to create.
They force you to put your assumptions
in front of everyone to see,
including yourself.
Sometimes the act of creating a concurrency diagram
can change your own mind
about how different parts of a system should be sequenced.
All you need are boxes, arrows and text.&lt;/p&gt;
&lt;h3 id="anatomy-of-a-concurrency-diagram"&gt;Anatomy of a concurrency diagram&lt;/h3&gt;
&lt;p&gt;A concurrency diagram is a simplified representation of your system,
plotted with time as one axis.
The other axis
separates different functional components;
what those are depends on your system
and the level of granularity
that you&amp;rsquo;re representing it in.
It&amp;rsquo;s similar to a &lt;a href="https://en.wikipedia.org/wiki/Gantt_chart"&gt;Gantt chart&lt;/a&gt; in many ways,
although there are differences.&lt;/p&gt;
&lt;p&gt;I like to have time running down the &lt;em&gt;y&lt;/em&gt; axis,
from top to bottom.
This is different to a Gantt chart
where time runs from left to right,
along the &lt;em&gt;x&lt;/em&gt; axis.
Plotting time on the &lt;em&gt;y&lt;/em&gt; axis instead
lets you align concurrent tasks
while making more efficient use of vertical space,
because every box only consumes one line of text
in that dimension.
It also feels more intuitive to me,
although that may just be familiarity.&lt;/p&gt;
&lt;p&gt;The &lt;em&gt;x&lt;/em&gt; axis of functional components
can get messy if there&amp;rsquo;s a lot going on,
so I like to subdivide it into columns
representing layers of architecture.
You might have a column for clients,
a column for your api,
another column for data storage
and so on.&lt;/p&gt;
&lt;p&gt;After that,
all you need are boxes and arrows.
Each box represents a thing that happens;
it could be a function,
an api endpoint,
an SQL insert statement
or literally anything that can be measured in time.
The diagram does not model how long each thing takes,
which is another difference to Gantt charts,
so all boxes are the same size.
Instead it uses the relative position of boxes
to identify things that occur concurrently
and arrows to identify blocking relationships.&lt;/p&gt;
&lt;p&gt;That may all sound quite abstract,
so here&amp;rsquo;s a concrete example
for part of a side-project
I&amp;rsquo;ve been working on lately:&lt;/p&gt;
&lt;p&gt;&lt;a href="https://assets.philbooth.me/images/concurrency.png"&gt;&lt;img alt="Concurrency diagram for a code analysis project" src="https://assets.philbooth.me/images/concurrency.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The details aren&amp;rsquo;t too important here,
but notice how concurrent operations are vertically aligned
and arrows indicate where blocking occurs.
If two people hold different assumptions
about which parts of this system
should run concurrently,
they will be swiftly eliminated
by bringing the diagram into discussion.&lt;/p&gt;
&lt;p&gt;If your team produces,
agrees on,
advertises
&lt;em&gt;and continues to maintain&lt;/em&gt;
diagrams like these,
it should go a long way to ensuring
that everyone pulls in the same direction.&lt;/p&gt;
&lt;aside&gt;
  &lt;p class="aside"&gt;
    &lt;em&gt;
      &lt;strong&gt;EDIT:&lt;/strong&gt;
      Thanks to &lt;a href="https://www.reddit.com/r/programming/comments/1h3d0tu/comment/lzrf5z8/"&gt;chucker23n&lt;/a&gt;
      and &lt;a href="https://www.reddit.com/r/programming/comments/1h3d0tu/comment/lzs2wvj/"&gt;hejle&lt;/a&gt;
      for pointing out the diagrams I describe here are not a new idea.
      They're a minor variation on UML &lt;a href="https://en.wikipedia.org/wiki/Sequence_diagram"&gt;sequence diagrams&lt;/a&gt;
      and &lt;a href="https://en.wikipedia.org/wiki/Activity_diagram"&gt;activity diagrams&lt;/a&gt;.
      I don't use either of those in my day-to-day,
      although I have seen sequence diagrams before
      and in hindsight,
      I was probably subconsciously influenced by them
      when writing this.
      It doesn't change the main thrust of this post though,
      which is to say that
      making concurrency explicit in a diagram
      is a good thing,
      regardless of what you call it
      or what shape the boxes are!
    &lt;/em&gt;
  &lt;/p&gt;
&lt;/aside&gt;</description><author>Phil Booth's Blog</author><pubDate>Sat, 30 Nov 2024 15:40:52 GMT</pubDate><guid isPermaLink="true">https://philbooth.me/blog/concurrency-diagrams/</guid></item><item><title>VIM Macros are Overrated</title><link>https://www.davidpriver.com/vim-macros-overrated.html</link><description>VIM Macros are Overrated



&lt;p&gt;
&lt;a href="https://www.davidpriver.com"&gt;Home&lt;/a&gt;
&lt;/p&gt;
&lt;h1 id="vim-macros-are-overrated"&gt;VIM Macros are Overrated&lt;/h1&gt;
&lt;p&gt;
&lt;i&gt;&lt;a href="mailto:david@davidpriver.com"&gt;David Priver&lt;/a&gt;, Nov 29th, 2024&lt;/i&gt;
&lt;/p&gt;
&lt;article&gt;

&lt;div&gt;
&lt;h2 id="macros"&gt;Macros&lt;/h2&gt;
&lt;p&gt;
I'm a big VIM user and enjoy working in it, both professionally and
recreationally. One of vim's nice features is the ability to record macros into
a register and then replay it. As the macros can include motion, you can set
them up so that they can be easily repeated and use a count modifier, like
&lt;tt&gt;22@w&lt;/tt&gt; to apply them multiple times. Do &lt;tt&gt;:h complex-repeat&lt;/tt&gt; to
learn more.
&lt;/p&gt;
&lt;p&gt;
However, there are some drawbacks to VIM macros. If you have a long or
complicated sequence of actions, it can be error prone to record the exact
macro needed to do what you want. They are stored in normal registers, so you
can paste them into a buffer, edit them and then yank them back into a
register, but that is pretty annoying and they quickly get unreadable.
&lt;/p&gt;
&lt;p&gt;
Another common pitfall is you realize after executing the macro that you need
some small thing changed or some additional transform. You then have to either
edit the macro or record a new one from scratch. Do this a few times and you'll
find you spent more time trying to record the perfect macro than just doing the
text operation. And good luck debugging it if your macro isn't quite right.
&lt;/p&gt;
&lt;p&gt;
If only we could write the text transforms as code. Wait a minute...
&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h2 id="filters"&gt;Filters&lt;/h2&gt;
&lt;p&gt;
Vim also has the feature that you can shell out to arbitrary programs,
writing to their stdin and reading from their stdout. You can feed them an
entire buffer or a selection of a buffer. The input lines are then replaced
with whatever the program wrote to stdout. Vim refers to these as "filters"
and invoke them with filter commands. Do &lt;tt&gt;:h filter&lt;/tt&gt; to learn more.
&lt;/p&gt;
&lt;p&gt;
In my experience, a better alternative to recording a complicated macro is to
just write a simple python script as the filter program.
As we are just writing normal code, this means it can be
arbitrarily complicated and you could even do syntactic/semantic analysis for
your custom text transforms. Additionally, it's just normal code instead of
weird editor commands so you can easily test and debug the transform if it's
getting hairy, write functions, break it up into multiple statements, etc.
&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h2 id="an-example"&gt;An Example&lt;/h2&gt;
&lt;p&gt;
Recently I was working with some C code where I needed to generate an X-macro
from a list of names. Example input would look like:
&lt;/p&gt;
&lt;div&gt;
&lt;pre&gt;
Potion of Healing
Wand of Fireball
Magic Sword
...  etc ...

&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;
I would like this to be converted to an X-macro, defining various forms of the names:
&lt;/p&gt;
&lt;div&gt;
&lt;pre&gt;
// #define X(UPPER, Title, snake, string)
#define XNames(X) \
  X(POTION_OF_HEALING, Potion_of_Healing, potion_of_healing, "Potion of Healing") \
  X(WAND_OF_FIREBALL, Wand_of_Fireball, wand_of_fireball, "Wand of Fireball") \
  X(MAGIC_SWORD, Magic_Sword, magic_sword, "Magic Sword") \
  ... etc ...

&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;
You can do this with vim commands and macros, but it is not easy. But behold, the simple
python script that does this conversion:
&lt;/p&gt;
&lt;pre class="python"&gt;
&lt;span class="import"&gt;import&lt;/span&gt; sys
&lt;span class="control"&gt;for&lt;/span&gt; line &lt;span class="keyword"&gt;in&lt;/span&gt; sys.stdin:
    l = line.strip()
    s = l.replace(&lt;span class="string"&gt;' '&lt;/span&gt;, &lt;span class="string"&gt;'_'&lt;/span&gt;)
    print(f&lt;span class="string"&gt;'    X({s.upper()}, {s}, {s.lower()}, "{l}") \\'&lt;/span&gt;)

&lt;/pre&gt;
&lt;p&gt;
Simple, easy to understand code. You can debug it, you can throw examples at
it, whatever. I usually name these little scripts f.py in my working directly
and invoke them with &lt;tt&gt;:'&amp;lt;,'&amp;gt;!python3 f.py&lt;/tt&gt;.
&lt;/p&gt;
&lt;p&gt;
That's it! When you find yourself trying to setup a complicated macro, stop
and think if it would be easier to express it as a little script that
processes stdin instead. Most of the time, the answer is yes.
&lt;/p&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;h2 id="copyright"&gt;Copyright&lt;/h2&gt;
&lt;p&gt;
All code in this article is released into the public domain.
&lt;/p&gt;
&lt;/div&gt;</description><author>DavidsBlog</author><pubDate>Sat, 30 Nov 2024 07:45:25 GMT</pubDate><guid isPermaLink="true">https://www.davidpriver.com/vim-macros-overrated.html</guid></item><item><title>SpiceNice - An Open Source Spice Database</title><link>https://www.tderflinger.com/en/spicenice-open-source-culinary-spice-database</link><description>Spices have shaped human civilization, driving exploration, trade, and culinary innovation for millennia. Today, I'm excited to introduce SpiceNice, a comprehensive open-source database that brings structured information about culinary spices and their source plants to everyone – from professional chefs to home cooks, botanists to farmers. A section on free literature about spices completes the website.</description><author>Thoughts by Thomas Derflinger</author><pubDate>Sat, 30 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.tderflinger.com/en/spicenice-open-source-culinary-spice-database</guid></item><item><title>Queenpins: Entertaining and Surprisingly Informative</title><link>https://olshansky.info/movie/queenpins/</link><description>Olshansky's review of Queenpins</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 30 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/movie/queenpins/</guid></item><item><title>Render HTML and CSS if JavaScript Is Disabled Using the `&amp;lt;noscript&amp;gt;` Tag</title><link>https://nelson.cloud/render-html-and-css-if-javascript-is-disabled-using-the-noscript-tag/?ref=rss</link><description>Render HTML and apply CSS styles if JavaScript is disabled.</description><author>Nelson Figueroa</author><pubDate>Sat, 30 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/render-html-and-css-if-javascript-is-disabled-using-the-noscript-tag/?ref=rss</guid></item><item><title>Tvorba webů 101</title><link>https://chamik.eu/smrst2024/</link><description>Dodatky prezentace Tvorba webů 101 pro Smršť 2024</description><author>Kubíkovo</author><pubDate>Sat, 30 Nov 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://chamik.eu/smrst2024/</guid></item><item><title>Will AI Take My Job?</title><link>https://smcleod.net/2024/11/will-ai-take-my-job/</link><description>It's probably not so much AI itself you have to fear.</description><author>smcleod.net</author><pubDate>Sat, 30 Nov 2024 00:00:02 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/11/will-ai-take-my-job/</guid></item><item><title/><link/><description>Perfect example of why RSS-based timelines can replace social media from big tech. Today I got surprised by this unexpected beauty on my timeline. Same social vibe, no dark patterns. Follow websites directly, straight from the horse's mouth. #reallysocialsites</description><author>My First Timeline</author><pubDate>Fri, 29 Nov 2024 15:54:41 GMT</pubDate><guid isPermaLink="true"/></item><item><title>On being a hello-er</title><link>https://dostoynikov.bearblog.dev/on-being-a-hello-er/</link><description>&lt;p&gt;Although I love talking with people—whether it’s having deep chats or just exchanging simple hellos—initiating communication has never been my strong suit. Most of the time, I’ve remained passive in first interactions. However, whenever someone else took the first step, I always did my best to keep the conversation lively and meaningful.&lt;/p&gt;
&lt;p&gt;After moving back to Japan recently and settling in the countryside, I’ve started noticing the power of a simple "hello" while wandering around the neighborhood. It makes me genuinely happy to receive these random greetings from strangers, accompanied by a smile. Inspired by this, I decided to try it myself.&lt;/p&gt;
&lt;p&gt;And wow—turns out I might be more of an extrovert than I thought! Now, I don’t go overboard by greeting every single person I see (that would definitely be a bit creepy), but I’ve realized that you can often sense the vibe of another person and gauge whether they might be open to a friendly gesture. When I feel it’s appropriate, I say hello and slightly bow my head to passersby. So far, I haven’t received any negative reactions—just smiles, nods, and warm greetings in return.&lt;/p&gt;
&lt;p&gt;For now, I’m enjoying my role as a "hello-er" and want to work on improving my overall communication skills. While I’m typically the more dominant side in conversations, taking the first step has always been a challenge for me. I figured that becoming a "hello-er" could be the first small but meaningful step toward overcoming this. Let’s see how it goes.&lt;/p&gt;</description><author>ᓚᘏᗢdostoynikov</author><pubDate>Fri, 29 Nov 2024 13:58:32 GMT</pubDate><guid isPermaLink="true">https://dostoynikov.bearblog.dev/on-being-a-hello-er/</guid></item><item><title>Be Indistractable</title><link>https://martinrue.com/be-indistractable</link><description>We live in a mega distracting world. Being indistractable is rare – so rare it may even be your edge.</description><author>Martin Rue</author><pubDate>Fri, 29 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/be-indistractable</guid></item><item><title>Branding that Means Business: A great reader that's worth reading earlier in your career</title><link>https://olshansky.info/book/branding_that_means_business/</link><description>Olshansky's review of Branding that Means Business</description><author>🦉 olshansky 🦁</author><pubDate>Fri, 29 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/book/branding_that_means_business/</guid></item><item><title>Getting Started with SurrealDB 2 and Axum for Web Development</title><link>https://spacedimp.com/blog/using-rust-axum-surrealdb-to-build-a-webapp/</link><description>&lt;p&gt;&lt;img alt="Alt text" src="https://spacedimp.com/img/gif/surreal.gif" title="demo of Rust, Axum, SurrealDB" /&gt;&lt;/p&gt;
&lt;p&gt;The goal of this blog post is to help beginners grasp the basics of using SurrealDB 2 and Axum for web development.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I find the technology fascinating, but readers should be aware that SurrealDB v1 upset many users due to the lack of documentation and poor performance&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;However, version 2 fixes several performance and bug issues. One such improvement is that the in-memory storage is now powered by SurrealKV, which is concurrent, meaning that read/write operations scale with the number of CPUs.&lt;/p&gt;
&lt;h2 id="understanding-surrealdb-s-architecture"&gt;Understanding SurrealDB's Architecture&lt;/h2&gt;
&lt;p&gt;SurrealDB separates its query language (called &lt;a href="https://surrealdb.com/docs/surrealql" rel="noopener" target="_blank"&gt;SurrealQL&lt;/a&gt;) from its data storage backend. This flexibility allows you to choose the best storage option for your project. While I'll be using SurrealKV locally for simplicity, &lt;a href="https://docs.rs/surrealdb/latest/surrealdb/engine/any/fn.connect.html#examples" rel="noopener" target="_blank"&gt;other options&lt;/a&gt; are available, such as TikV for distributed storage or other in-memory solutions.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://surrealdb.com/docs/surrealql" rel="noopener" target="_blank"&gt;SurrealQL&lt;/a&gt; from the data storage. I'll be using SurrealKV locally but there are other options including TikV for distributed and in memory to name a &lt;a href="https://docs.rs/surrealdb/latest/surrealdb/engine/any/fn.connect.html#examples" rel="noopener" target="_blank"&gt;few&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Check out the SurrealDB &lt;a href="https://surrealdb.com/docs/surrealdb/introduction/start" rel="noopener" target="_blank"&gt;getting started&lt;/a&gt; page to start up an in memory data store. I would recommend clicking on "Using CLI" to learn how to connect to the database in the terminal.&lt;/p&gt;
&lt;p&gt;I found the &lt;a href="https://surrealdb.com/learn/book" rel="noopener" target="_blank"&gt;book&lt;/a&gt; very helpful in learning the language.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before getting started, make sure you have the following installed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.rust-lang.org/tools/install" rel="noopener" target="_blank"&gt;Rust&lt;/a&gt; installed&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/surrealdb/surrealdb?tab=readme-ov-file#installation" rel="noopener" target="_blank"&gt;SurrealDB&lt;/a&gt; installed&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="step-1-create-the-project"&gt;Step 1. Create the Project&lt;/h2&gt;
&lt;pre class="language-bash " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #e06c75;"&gt;cargo&lt;/span&gt;&lt;span&gt; new webapp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;add dependencies&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #56b6c2;"&gt;cd&lt;/span&gt;&lt;span&gt; webapp
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;cargo&lt;/span&gt;&lt;span&gt; add axum
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;cargo&lt;/span&gt;&lt;span&gt; add serde&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --features&lt;/span&gt;&lt;span&gt; derive
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;cargo&lt;/span&gt;&lt;span&gt; add surrealdb&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --features&lt;/span&gt;&lt;span&gt; kv-surrealkv
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;cargo&lt;/span&gt;&lt;span&gt; add thiserror
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;cargo&lt;/span&gt;&lt;span&gt; add tokio&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --features&lt;/span&gt;&lt;span&gt; full
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;cargo&lt;/span&gt;&lt;span&gt; add serde_json
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;p&gt;Now start up a SurrealKV storage engine at localhost:8080&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #e06c75;"&gt;surreal&lt;/span&gt;&lt;span&gt; start&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --user&lt;/span&gt;&lt;span&gt; root&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --pass&lt;/span&gt;&lt;span&gt; password&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --bind&lt;/span&gt;&lt;span&gt; 127.0.0.1:8080 surrealkv://mydb
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In depth examples of using surreal start &lt;a href="https://surrealdb.com/docs/surrealdb/cli/start" rel="noopener" target="_blank"&gt;here&lt;/a&gt;. Make sure to select V2.x&lt;/p&gt;
&lt;p&gt;From a second terminal, connect to the engine and set the database to test_db and namespace to test_ns&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #e06c75;"&gt;surreal&lt;/span&gt;&lt;span&gt; sql&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --endpoint&lt;/span&gt;&lt;span&gt; http://127.0.0.1:8080&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --user&lt;/span&gt;&lt;span&gt; root&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --pass&lt;/span&gt;&lt;span&gt; password&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --ns&lt;/span&gt;&lt;span&gt; test_ns&lt;/span&gt;&lt;span style="color: #e06c75;"&gt; --db&lt;/span&gt;&lt;span&gt; test_db
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In depth examples of using surreal sql &lt;a href="https://surrealdb.com/docs/surrealdb/cli/sql" rel="noopener" target="_blank"&gt;here&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="step-2-insert-some-data-in-the-database"&gt;Step 2. Insert some data in the database&lt;/h2&gt;
&lt;p&gt;After the last surreal sql ... statement you should be connected:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #e06c75;"&gt;test_ns/test_db&lt;/span&gt;&lt;span&gt;&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I will create a &lt;a href="https://surrealdb.com/docs/surrealql/statements/define/table" rel="noopener" target="_blank"&gt;TABLE&lt;/a&gt; called users and set the table to have structured, predictable data types with a &lt;a href="https://surrealdb.com/docs/tutorials/define-a-schema" rel="noopener" target="_blank"&gt;Schema&lt;/a&gt;.&lt;/p&gt;
&lt;pre class="language-sql " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-sql"&gt;&lt;span&gt;DEFINE TABLE IF NOT EXISTS users SCHEMAFULL;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Scrolling down in that last Schema link will show the user that at any point we can add a field to our table that is schemaless using FLELXIBLE. Perhaps for metatdata.&lt;/p&gt;
&lt;p&gt;Let's add a username &lt;a href="https://surrealdb.com/docs/surrealql/statements/define/field" rel="noopener" target="_blank"&gt;FIELD&lt;/a&gt; to the table that's only allowed to be under 14 characters long&lt;/p&gt;
&lt;pre class="language-sql " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-sql"&gt;&lt;span&gt;DEFINE FIELD username ON TABLE users TYPE string ASSERT string::len($value) &amp;lt; &lt;/span&gt;&lt;span style="color: #d19a66;"&gt;14&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we can define an &lt;a href="https://surrealdb.com/docs/surrealql/statements/define/indexes" rel="noopener" target="_blank"&gt;INDEX&lt;/a&gt; for the username to make it a unique field.&lt;/p&gt;
&lt;p&gt;Reading through the docs tells us that indexes are used to speed up query execution times dramatically.&lt;/p&gt;
&lt;pre class="language-sql " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-sql"&gt;&lt;span&gt;DEFINE INDEX usernameIndex ON TABLE users COLUMNS username UNIQUE;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now let's &lt;a href="https://surrealdb.com/docs/surrealql/statements/create" rel="noopener" target="_blank"&gt;CREATE&lt;/a&gt; a user&lt;/p&gt;
&lt;pre class="language-sql " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-sql"&gt;&lt;span&gt;CREATE users &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;SET&lt;/span&gt;&lt;span&gt; username = &lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;User1&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and we can see from the output that SurrealDB automatically gives us an id field&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #e06c75;"&gt;[[{&lt;/span&gt;&lt;span&gt; id: users:2272f7xnqi502d9it9q4, username: &lt;/span&gt;&lt;span style="color: #98c379;"&gt;'User1' &lt;/span&gt;&lt;span&gt;}&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;]]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;we could have also created the unique id with this command (skip, just for example):&lt;/p&gt;
&lt;pre class="language-sql " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-sql"&gt;&lt;span&gt;CREATE users:&lt;/span&gt;&lt;span style="color: #d19a66;"&gt;1 &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;SET&lt;/span&gt;&lt;span&gt; username = &lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;User1&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;br /&gt;
&lt;h2 id="step-3-write-some-rust-code"&gt;Step 3. Write some Rust code&lt;/h2&gt;
&lt;p&gt;Open src/main.rs and grab all the dependencies we added&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;axum::{extract::State, routing::get, Router, Json, extract::Path, response::IntoResponse };
&lt;/span&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;surrealdb::Surreal;
&lt;/span&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;serde::{Deserialize, Serialize};
&lt;/span&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;std::sync::Arc;
&lt;/span&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;tokio::sync::Mutex;
&lt;/span&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;surrealdb::engine::any::Any;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We should pause here and take a look at the official SurrealDB example of using the &lt;a href="https://surrealdb.com/docs/sdk/rust/frameworks/axum" rel="noopener" target="_blank"&gt;Rust SDK&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Ok so it says we should convert Surreal errors into Axum responses (copy paste the boilerplate)&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #c678dd;"&gt;mod &lt;/span&gt;&lt;span&gt;error {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;axum::http::StatusCode;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;axum::response::IntoResponse;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;axum::response::Response;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;axum::Json;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;use &lt;/span&gt;&lt;span&gt;thiserror::Error;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    #[&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;derive&lt;/span&gt;&lt;span&gt;(Error, Debug)]
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;pub enum &lt;/span&gt;&lt;span&gt;Error {
&lt;/span&gt;&lt;span&gt;        #[&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;error&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;database error&amp;quot;&lt;/span&gt;&lt;span&gt;)]
&lt;/span&gt;&lt;span&gt;        Db,
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;impl &lt;/span&gt;&lt;span&gt;IntoResponse &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;for &lt;/span&gt;&lt;span&gt;Error {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;fn &lt;/span&gt;&lt;span style="color: #61afef;"&gt;into_response&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;self&lt;/span&gt;&lt;span&gt;) -&amp;gt; Response {
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;match &lt;/span&gt;&lt;span style="color: #e06c75;"&gt;self &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;            Error::Db=&amp;gt; (StatusCode::&lt;/span&gt;&lt;span style="color: #d19a66;"&gt;INTERNAL_SERVER_ERROR&lt;/span&gt;&lt;span&gt;, Json(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;An error has occurred. Please try again later.&amp;quot;&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;to_string&lt;/span&gt;&lt;span&gt;())).&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;into_response&lt;/span&gt;&lt;span&gt;(),
&lt;/span&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;impl &lt;/span&gt;&lt;span&gt;From&amp;lt;surrealdb::Error&amp;gt; &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;for &lt;/span&gt;&lt;span&gt;Error {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;fn &lt;/span&gt;&lt;span style="color: #61afef;"&gt;from&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;error&lt;/span&gt;&lt;span&gt;: surrealdb::Error) -&amp;gt; &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;Self &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;            eprintln!(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #d19a66;"&gt;{error}&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;Self&lt;/span&gt;&lt;span&gt;::Db
&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alright with the boilerplate out of the way I want to discuss &lt;a href="https://surrealdb.com/blog/introducing-surrealany-dynamic-support-for-any-engine-in-rust" rel="noopener" target="_blank"&gt;surrealdb::engine::any&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I mentioned that I would be using SurrealKV as the database engine, but really SurrealDB doesn't force us to stick with one. The link right above explains how the query language is completely separate from the database engine.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-rust"&gt;&lt;span style="font-style: italic; color: #5c6370;"&gt;// make sure our database is accessible from all routes
&lt;/span&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;derive&lt;/span&gt;&lt;span&gt;(Clone)]
&lt;/span&gt;&lt;span style="color: #c678dd;"&gt;struct &lt;/span&gt;&lt;span&gt;AppState {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #e06c75;"&gt;db&lt;/span&gt;&lt;span&gt;: Arc&amp;lt;Mutex&amp;lt;Surreal&amp;lt;Any&amp;gt;&amp;gt;&amp;gt;,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre class="language-rust " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;tokio&lt;/span&gt;&lt;span&gt;::&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;main&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span&gt;async &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;fn &lt;/span&gt;&lt;span style="color: #61afef;"&gt;main&lt;/span&gt;&lt;span&gt;() -&amp;gt; Result&amp;lt;(), error::Error&amp;gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-style: italic; color: #5c6370;"&gt;// any allows us to swap between rocksdb://mydb, mem://, etc. 
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;let&lt;/span&gt;&lt;span&gt; db = surrealdb::engine::any::connect(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;surrealkv://mydb&amp;quot;&lt;/span&gt;&lt;span&gt;).await?;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="font-style: italic; color: #5c6370;"&gt;// authenticate
&lt;/span&gt;&lt;span&gt;db.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;signin&lt;/span&gt;&lt;span&gt;(surrealdb::opt::auth::Root {
&lt;/span&gt;&lt;span&gt;    username: &lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;root&amp;quot;&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;    password: &lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;password&amp;quot;&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;}).await?;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="font-style: italic; color: #5c6370;"&gt;// select a namespace and a database inside that namespace
&lt;/span&gt;&lt;span&gt;db.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;use_ns&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;test_ns&amp;quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;use_db&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;test_db&amp;quot;&lt;/span&gt;&lt;span&gt;).await?;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="font-style: italic; color: #5c6370;"&gt;// for routes to access the database, Axum provides AppState
&lt;/span&gt;&lt;span style="color: #c678dd;"&gt;let&lt;/span&gt;&lt;span&gt; app_state = AppState {
&lt;/span&gt;&lt;span&gt;    db: Arc::new(Mutex::new(db))
&lt;/span&gt;&lt;span&gt;};
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="font-style: italic; color: #5c6370;"&gt;// define our routes to create, delete, get users
&lt;/span&gt;&lt;span style="color: #c678dd;"&gt;let&lt;/span&gt;&lt;span&gt; app = Router::new()
&lt;/span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;route&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;/&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;get&lt;/span&gt;&lt;span&gt;(get_users))
&lt;/span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;route&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;/create/:uname&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;get&lt;/span&gt;&lt;span&gt;(create_user))
&lt;/span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;route&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;/delete/:uname&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;get&lt;/span&gt;&lt;span&gt;(delete_user))
&lt;/span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;with_state&lt;/span&gt;&lt;span&gt;(app_state);
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #c678dd;"&gt;let&lt;/span&gt;&lt;span&gt; listener = tokio::net::TcpListener::bind(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;0.0.0.0:3000&amp;quot;&lt;/span&gt;&lt;span&gt;).await.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;unwrap&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;axum::serve(listener, app).await.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;unwrap&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;Ok(())
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For brevity I'll just use get requests with the data directly in the path.&lt;/p&gt;
&lt;p&gt;However, you can use RESTful APIs such as delete, post, etc. Then access the &lt;a href="https://surrealdb.com/docs/surrealdb/integration/http" rel="noopener" target="_blank"&gt;endpoints&lt;/a&gt; with curl&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #e06c75;"&gt;curl -I&lt;/span&gt;&lt;span&gt; http://localhost:8080/status
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;HTTP/1.1&lt;/span&gt;&lt;span&gt; 200 OK
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;content-length:&lt;/span&gt;&lt;span&gt; 0
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;vary:&lt;/span&gt;&lt;span&gt; origin, access-control-request-method, access-control-request-headers
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;access-control-allow-origin: &lt;/span&gt;&lt;span&gt;*
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;surreal-version:&lt;/span&gt;&lt;span&gt; surrealdb-2.0.0+20240910.8f30ee08
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;server:&lt;/span&gt;&lt;span&gt; SurrealDB
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;x-request-id:&lt;/span&gt;&lt;span&gt; 3dedcc96-4d8a-451e-b60d-4eaac14fa3f8
&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;date:&lt;/span&gt;&lt;span&gt; Wed, 11 Sep 2024 00:52:49 GMT
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we should write a database query to define our table if it doesn't exist. Just like we did manually at the start of the tutorial.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;db.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;query&lt;/span&gt;&lt;span&gt;(
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;DEFINE TABLE IF NOT EXISTS users SCHEMAFULL;
&lt;/span&gt;&lt;span style="color: #98c379;"&gt;    DEFINE FIELD IF NOT EXISTS username ON TABLE users TYPE string ASSERT string::len($value) &amp;lt; 14;
&lt;/span&gt;&lt;span style="color: #98c379;"&gt;    DEFINE INDEX IF NOT EXISTS usernameIndex ON TABLE users COLUMNS username UNIQUE;
&lt;/span&gt;&lt;span style="color: #98c379;"&gt;    &amp;quot;
&lt;/span&gt;&lt;span&gt;).await?;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now we create the routes&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-rust"&gt;&lt;span style="font-style: italic; color: #5c6370;"&gt;// schema to insert or get users 
&lt;/span&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;derive&lt;/span&gt;&lt;span&gt;(Serialize, Deserialize, Clone)]
&lt;/span&gt;&lt;span style="color: #c678dd;"&gt;pub struct &lt;/span&gt;&lt;span&gt;User {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #e06c75;"&gt;username&lt;/span&gt;&lt;span&gt;: String,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="font-style: italic; color: #5c6370;"&gt;// retrieve all users 
&lt;/span&gt;&lt;span style="font-style: italic; color: #5c6370;"&gt;// 127.0.0.1:3000/
&lt;/span&gt;&lt;span&gt;async &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;fn &lt;/span&gt;&lt;span style="color: #61afef;"&gt;get_users&lt;/span&gt;&lt;span&gt;(State(&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;state&lt;/span&gt;&lt;span&gt;): State&amp;lt;AppState&amp;gt;) -&amp;gt; Result&amp;lt;Json&amp;lt;Vec&amp;lt;User&amp;gt;&amp;gt;, error::Error&amp;gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;let&lt;/span&gt;&lt;span&gt; db = state.db.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;lock&lt;/span&gt;&lt;span&gt;().await;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;let mut&lt;/span&gt;&lt;span&gt; response = db.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;SELECT * FROM users&amp;quot;&lt;/span&gt;&lt;span&gt;).await?;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;let&lt;/span&gt;&lt;span&gt; result: Vec&amp;lt;User&amp;gt; = response.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;take&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d19a66;"&gt;0&lt;/span&gt;&lt;span&gt;)?;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    Ok(Json(result))
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now to create a user at localhost:3000/create/newusername&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;async &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;fn &lt;/span&gt;&lt;span style="color: #61afef;"&gt;create_user&lt;/span&gt;&lt;span&gt;(Path(&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;uname&lt;/span&gt;&lt;span&gt;): Path&amp;lt;String&amp;gt;, State(&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;state&lt;/span&gt;&lt;span&gt;): State&amp;lt;AppState&amp;gt;) -&amp;gt; Result&amp;lt;impl IntoResponse, error::Error&amp;gt;{
&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;let&lt;/span&gt;&lt;span&gt; db = state.db.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;lock&lt;/span&gt;&lt;span&gt;().await;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;let&lt;/span&gt;&lt;span&gt; newUser: Option&amp;lt;User&amp;gt; = db.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;users&amp;quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;content&lt;/span&gt;&lt;span&gt;(User {
&lt;/span&gt;&lt;span&gt;        username: uname,
&lt;/span&gt;&lt;span&gt;    }).await?;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    Ok(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;Success creating new user&amp;quot;&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we can delete a user at localhost:3000/delete/newusername&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282c34; color: #abb2bf;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;async &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;fn &lt;/span&gt;&lt;span style="color: #61afef;"&gt;delete_user&lt;/span&gt;&lt;span&gt;(Path(&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;uname&lt;/span&gt;&lt;span&gt;): Path&amp;lt;String&amp;gt;, State(&lt;/span&gt;&lt;span style="color: #e06c75;"&gt;state&lt;/span&gt;&lt;span&gt;): State&amp;lt;AppState&amp;gt;) -&amp;gt; Result&amp;lt;impl IntoResponse, error::Error&amp;gt;{
&lt;/span&gt;&lt;span&gt;   &lt;/span&gt;&lt;span style="color: #c678dd;"&gt;let&lt;/span&gt;&lt;span&gt; db = state.db.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;lock&lt;/span&gt;&lt;span&gt;().await;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    db.&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;query&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;DELETE FROM users WHERE username = $username&amp;quot;&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;        .&lt;/span&gt;&lt;span style="color: #56b6c2;"&gt;bind&lt;/span&gt;&lt;span&gt;((&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;username&amp;quot;&lt;/span&gt;&lt;span&gt;, uname)).await?;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    Ok(&lt;/span&gt;&lt;span style="color: #98c379;"&gt;&amp;quot;Success deleting user&amp;quot;&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this basic setup in place, you can start experimenting with more complex queries, adding authentication, or introducing new features to your app. The full code is available on &lt;a href="https://github.com/spacedimp/Rust-Axum-SurrealDB-example/tree/main" rel="noopener" target="_blank"&gt;Github&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Happy coding!&lt;/p&gt;</description><author/><pubDate>Fri, 29 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://spacedimp.com/blog/using-rust-axum-surrealdb-to-build-a-webapp/</guid></item><item><title>Margin Call</title><link>https://mehulkar.com/blog/2024/11/margin-call?utm_source=rss</link><description>&lt;div class="letterboxd-movie-data-content"&gt;
   &lt;p&gt;&lt;img src="https://a.ltrbxd.com/resized/sm/upload/85/1q/1m/jj/margin-call-0-600-0-900-crop.jpg?v=1b5d43ed27" /&gt;&lt;/p&gt; &lt;p&gt;Watched on Friday November 29, 2024.&lt;/p&gt; 
  &lt;p&gt;Rated 3 stars.&lt;/p&gt;&lt;p&gt;
  &lt;/p&gt;&lt;div class="float-clear"&gt;&lt;/div&gt;
&lt;/div&gt;

        &lt;p&gt;Thanks for reading this post via RSS!&lt;/p&gt;</description><author>Mehul Kar's blog</author><pubDate>Fri, 29 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://mehulkar.com/blog/2024/11/margin-call?utm_source=rss</guid></item><item><title>Year Day is December 11</title><link>https://taylor.town/year-day</link><description>It's your yearly nudge to solicit positive feedback.</description><author>taylor.town</author><pubDate>Fri, 29 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/year-day</guid></item><item><title>Festive offers for books on Python, Linux, Regular Expressions, Vim and more!</title><link>https://learnbyexample.github.io/programming-deals-2024/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;Here are some awesome deals for programming books and courses during the 2024 festive season.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="my-ebooks"&gt;My ebooks&lt;a class="zola-anchor" href="#my-ebooks"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Offers valid till 02-Dec-2024. You can get them on Leanpub:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/learnbyexample-all-books/c/FestiveOffer"&gt;All 13 Books bundle&lt;/a&gt; — $15 (normal price $32), learn Regular Expressions, Linux CLI tools, Python, Vim and more!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/linux-cli-text-processing/c/FestiveOffer"&gt;Linux CLI Text Processing bundle&lt;/a&gt; — $10 (normal price $20), grep, sed, awk, perl and ruby one-liners, GNU coreutils, CLI computing&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/b/python-bundle/c/FestiveOffer"&gt;Learn by example Python bundle&lt;/a&gt; — $8 (normal price $15), Python introduction, Regular Expressions and Projects&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/py_regex/c/FestiveOffer"&gt;Understanding Python re(gex)?&lt;/a&gt; — FREE (normal price $10)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can also avail these offers on Gumroad:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"&gt;All 13 Books Bundle&lt;/a&gt; — $15 (normal price $32), learn Regular Expressions, Linux CLI tools, Python, Vim and more!&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/FestiveOffer"&gt;Linux CLI Text Processing bundle&lt;/a&gt; — $10 (normal price $20), grep, sed, awk, perl and ruby one-liners, GNU coreutils, CLI computing&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/python-bundle/FestiveOffer"&gt;Learn by example Python bundle&lt;/a&gt; — $8 (normal price $15), Python introduction, Regular Expressions and Projects&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/py_regex"&gt;Understanding Python re(gex)?&lt;/a&gt; — FREE (normal price $10)&lt;/li&gt;
&lt;/ul&gt;
&lt;p align="center"&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/FestiveOffer"&gt;&lt;img alt="All books bundle" src="/images/books/all_books_bundle.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="indie-creators"&gt;Indie creators&lt;a class="zola-anchor" href="#indie-creators"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mathspp.gumroad.com/l/all-books-bundle/BF24"&gt;7 Python books bundle&lt;/a&gt; — 50% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://driscollis.gumroad.com/"&gt;Python books&lt;/a&gt; — 35% off with &lt;code&gt;BF24&lt;/code&gt; discount code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://adamchainz.gumroad.com/"&gt;Ebooks on Django and Git&lt;/a&gt; — 50% off, plus purchasing power parity if applicable
&lt;ul&gt;
&lt;li&gt;see also author's &lt;a href="https://adamj.eu/tech/2024/11/18/django-black-friday-deals-2024/"&gt;blog post&lt;/a&gt; for links to other Django-related deals&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://thepythoncodingplace.thinkific.com/order?ct=20aec16f-ab0e-467f-bc1b-7e98e64d47f4"&gt;The Python Coding Place Membership&lt;/a&gt; — 40% off with &lt;code&gt;black2024&lt;/code&gt; discount code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.pythonmorsels.com/courses/jumpstart/overview/"&gt;Python Jumpstart&lt;/a&gt; — 50% launch discount
&lt;ul&gt;
&lt;li&gt;see also author's &lt;a href="https://treyhunner.com/2024/11/python-black-friday-and-cyber-monday-sales-2024/"&gt;blog post&lt;/a&gt; for links to other Python deals&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://wizardzines.com/"&gt;Wizard Zines&lt;/a&gt; — 50% off with &lt;code&gt;WIZARDPDF&lt;/code&gt; discount code&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shrutibalasa.gumroad.com/l/ebooks-combo/BLACKFRIDAY24"&gt;CSS Flex and Grid + Level up with Tailwind CSS&lt;/a&gt; — 60% off&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/everydayrailsrspec/c/rubyconf2024"&gt;Everyday Rails Testing with RSpec&lt;/a&gt; — 53% off&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="other-deals"&gt;Other deals&lt;a class="zola-anchor" href="#other-deals"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://media.pragprog.com/newsletters/2024-11-20.html"&gt;The Pragmatic Bookshelf&lt;/a&gt; — 40% off on all ebooks and audio books&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mailchi.mp/leanpub/weekly-sale-2024-nov-bf-5388297"&gt;Leanpub Black Friday Sale&lt;/a&gt; — offers for programming books, bundles and courses&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/trungdq88/Awesome-Black-Friday-Cyber-Monday"&gt;Huge list of awesome deals&lt;/a&gt; — tools, productivity, books, courses, etc&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/0x90n/InfoSec-Black-Friday"&gt;InfoSec Hack Friday&lt;/a&gt; — InfoSec related software/tools&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blackfridaydeals.dev/"&gt;blackfridaydeals.dev&lt;/a&gt; — Hottest Black Friday Deals for Developers&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Fri, 29 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/programming-deals-2024/</guid></item><item><title>Docker IPv6 Pitfalls, or "Why does my Traefik not see the Source IP for this service?"</title><link>https://gero.dev/blog/docker-ipv6-pitfalls</link><description>Configuring Docker to forward IPv6 requests to Traefik and making `ipAllowList` firewalls work</description><author>Gero Gerke</author><pubDate>Fri, 29 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://gero.dev/blog/docker-ipv6-pitfalls</guid></item><item><title>Home Network 4.0</title><link>http://blog.jonandnic.com/2024/11/28/home-network-4-0/</link><description>Version 4.0 of the network solves many of the issues I had maintaining and growing online capabilities. The new spine is provided by a Ubiquiti UXG Pro on a dedicated UPS that is practically bullet proof.</description><author>jonandnic dot com</author><pubDate>Fri, 29 Nov 2024 01:18:26 GMT</pubDate><guid isPermaLink="true">http://blog.jonandnic.com/2024/11/28/home-network-4-0/</guid></item><item><title>t2x - a CLI tool for AI-first text operations</title><link>https://shruggingface.com/microblog/2024/11/28/t2x-a-cli-tool-for-ai-first-text-operations</link><description>I've started hacking on a new open source CLI tool I'm calling t2x, short for "text to whatever". It uses language models (local and in the cloud) to perform useful text operations.</description><author>shruggingface.com</author><pubDate>Fri, 29 Nov 2024 00:26:00 GMT</pubDate><guid isPermaLink="true">https://shruggingface.com/microblog/2024/11/28/t2x-a-cli-tool-for-ai-first-text-operations</guid></item><item><title>Crashed a talk on moral luck and fairness today - it was great!</title><link>https://liza.io/crashed-a-talk-on-moral-luck-and-fairness-today-it-was-great/</link><description>&lt;p&gt;Today I went to a paper discussion about resultant moral luck. I did not know this would be a roundtable discussion. I thought it&amp;rsquo;d be a lecture. But it was even better!&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Thu, 28 Nov 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/crashed-a-talk-on-moral-luck-and-fairness-today-it-was-great/</guid></item><item><title>Video: Testing the Silk Platform in 2024 Interview (12 minutes)</title><link>https://tanelpoder.com/posts/testing-the-silk-platform-in-2024-interview/</link><description>&lt;p&gt;Here&amp;rsquo;s a 12-minute video of our chat with &lt;a href="https://silk.us"&gt;Silk&lt;/a&gt; VP of Product &lt;a href="https://www.linkedin.com/in/zen10440/"&gt;Tom O&amp;rsquo;Neill&lt;/a&gt; about my recent testing of the Silk Platform in Google Cloud.&lt;/p&gt;
&lt;div style="padding: 56.25% 0 0 0;"&gt;&lt;/div&gt;
&lt;p&gt;In this interview we cover some high level points, conclusions and talk a little bit about the future. If you want to read all the technical details and some interesting references, see the links below:&lt;/p&gt;</description><author>Tanel Poder Blog</author><pubDate>Thu, 28 Nov 2024 21:52:59 GMT</pubDate><guid isPermaLink="true">https://tanelpoder.com/posts/testing-the-silk-platform-in-2024-interview/</guid></item><item><title>Design Custom Candlestick Patterns for Signal Generation Using Python</title><link>https://blog.adnansiddiqi.me/design-custom-candlestick-patterns-for-signal-generation-using-python/</link><description>&lt;p&gt;This post is part of the T4p Series. In the previous post, I introduced you to Candlestick patterns, explaining what they are and discussing a couple of well-known patterns and their implementation in Python. We will be discussing further famous patterns in coming posts but here we will learn how you can come up with your own candlestick patterns and implement them in Python. Ultimately, we will be using our custom and awesome candlestick pattern to generate signals and print money. Custom Pattern So, what would that custom pattern be? Pretty simple: Bullish: If 3 consecutive candles are green, it triggers a SELL signal. Bearish: If 3 consecutive candles are red, it triggers a BUY signal (Buy the dip!). Alright, now that we’ve created our revolutionary pattern, it’s time to automate it. Let’s dive in! Development Visualization Like always, the first step is to visualize the candlestick data. used ChatGPT to generate fictitious OHLC data tailored to my requirements. Calling generate_signals function produced the following output: Our pattern generates bearish or bullish signals if there are 3 consecutive green or red candles, respectively. In the chart above, the first red candle appeared on 2nd October, and the 3rd consecutive red candle appeared on 4th October, triggering a BUY signal. Similarly, the 3rd consecutive green candle appeared on the 14th and 16th of October, triggering a SELL signal. Below is the code that generates these signals: def generate_signals(df): signals = [] dates = [] # To store proper dates # Ensure the Date column or index is in datetime format if not isinstance(df.index, pd.DatetimeIndex): df.index = pd.to_datetime(df.index) for i in range(len(df)): if i &amp;#62;= 2: # Start checking from the 3rd row # Check for 3 consecutive green candles -&amp;#62; Generate SELL signal if ( df.iloc[i-2]["Close"] &amp;#62; df.iloc[i-2]["Open"] and df.iloc[i-1]["Close"] &amp;#62; df.iloc[i-1]["Open"] and df.iloc[i]["Close"] &amp;#62; df.iloc[i]["Open"] ): signals.append("SELL") dates.append(df.index[i]) # Add the proper date continue # Check for 3 consecutive red candles -&amp;#62; Generate BUY signal if ( df.iloc[i-2]["Close"] &amp;#60; df.iloc[i-2]["Open"] and df.iloc[i-1]["Close"] &amp;#60; df.iloc[i-1]["Open"] and df.iloc[i]["Close"] &amp;#60; df.iloc[i]["Open"] ): signals.append("BUY") dates.append(df.index[i]) # Add the proper date continue # Default to NEUTRAL if no pattern detected signals.append("NEUTRAL") dates.append(df.index[i]) # Add the proper date # Add padding for the first two rows (no signals there) signals = ["NEUTRAL", "NEUTRAL"] + signals dates = list(df.index[:2]) + dates # Ensure the dates align return pd.DataFrame({"Date": dates[:len(df)], "Signal": signals[:len(df)]}) And when you run it, it produces the following output: &amp;#160; Sweet, No? Conclusion Now you’ve learned how to easily automate your own favorite and secret patterns in Python and generate signals. Typically, traders rely on well-known patterns, which we will discuss in future posts. As always, the notebook and relevant data have been uploaded to GitHub.&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/design-custom-candlestick-patterns-for-signal-generation-using-python/"&gt;Design Custom Candlestick Patterns for Signal Generation Using Python&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Thu, 28 Nov 2024 09:20:47 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/design-custom-candlestick-patterns-for-signal-generation-using-python/</guid></item><item><title>Whither poetry?</title><link>https://gallant.dev/posts/whither-poetry/</link><description>&lt;p&gt;I have a soft spot for poetry.&lt;/p&gt;
&lt;p&gt;That is what poems are for - sentimental musings and meditative cogitation. These are perennial features of humanity, and well worth the time.&lt;/p&gt;
&lt;p&gt;As we find ourselves in &lt;a href="https://en.wikipedia.org/wiki/May_you_live_in_interesting_times"&gt;interesting times&lt;/a&gt;, the gap between reality and the narrative of progress is growing, and applied human intelligence (aka "technology") is "disrupting" (aka changing without full context) many things. Where does this leave the poet?&lt;/p&gt;
&lt;!-- TEASER_END --&gt;

&lt;!-- https://artist.scop.io/image/black-and-white-flower-sketch-4 --&gt;

&lt;p&gt;&lt;img alt="Fading flower - grayscale closeup of a lily with only petals visible" src="https://gallant.dev/images/fading_flower.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Most considerations of modern affairs involve some form of economics. Poetry, largely, doesn't.&lt;/p&gt;
&lt;p&gt;What’s more, poetry is just a sequence of tokens, in surprising-yet-retrospectively-intuitive patterns. These patterns can be probabilistically modeled and effectively mimicked. &lt;a href="https://en.wikipedia.org/wiki/Large_language_model"&gt;Sound familiar&lt;/a&gt;?&lt;/p&gt;
&lt;p&gt;People were "disrupting" poetry long before modern LLMs - a literary magazine was tricked into dedicating an entire issue to the nonexistent &lt;a href="https://daily.jstor.org/spectra-the-poetry-movement-that-was-all-a-hoax/"&gt;"Spectric School of Poetry"&lt;/a&gt; in &lt;strong&gt;1917&lt;/strong&gt;. When the experience is subjective and the construction adequately erudite, distinguishing "the real thing" from &lt;a href="https://www.elsewhere.org/journal/pomo/"&gt;literal word salad&lt;/a&gt; becomes impossible. (And no, &lt;a href="https://pdos.csail.mit.edu/archive/scigen/"&gt;science isn't immune&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;LLMs essentially lower the bar for on-demand text generation. What's more, LLMs are arguably truly good at poetry, for at least some definitions of "good" and "poetry." A recent study found that &lt;a href="https://www.nature.com/articles/s41598-024-76900-1"&gt;laypeople both could not identify AI-generated poetry, and preferred it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So we return to our question of where this leaves the poet - a human, who wishes to order words not via massive matrices and linear algebra, but their own neurons and synapses. Does human poetry have intrinsic value, despite its likely irrelevance to an external audience?&lt;/p&gt;
&lt;p&gt;Michele Elam argues that &lt;a href="https://gwern.net/doc/ai/nn/transformer/gpt/poetry/2023-elam.pdf"&gt;"poetry will not optimize"&lt;/a&gt; - that algorithms failed to understand and thus meaningfully create poetry akin to Maya Angelou's &lt;a href="https://www.poetryfoundation.org/poems/46446/still-i-rise"&gt;Still I Rise&lt;/a&gt;. This argument is both correct and, capitalistically, irrelevant - "Still I Rise" is a singular poem, and if in the general case most people cannot distinguish and even prefer algorithmic output, then history shows that is what they will have.&lt;/p&gt;
&lt;p&gt;This may seem low-stakes, but ultimately all creative writing is at stake here, and likely all artistic output regardless of medium. The primary difference of poetry is it is entirely linguistic, and generally short (certainly relative to LLM context windows) - but technological scale will doubtlessly claim more victims.&lt;/p&gt;
&lt;p&gt;It is easy to reject this as "capitalism bad" - and certainly, capitalism has shortcomings. But it also has enabled social mobility, and simply is the system we have. Any significant change would take time, or violence, and I'd prefer the former. And even in another system, what is being reflected here isn't simply money but society - the perspectives of other people (who didn't make the poem/book/art/song), who would traditionally be the "audience" but who now, overall, prefer other things.&lt;/p&gt;
&lt;p&gt;So, what is the value of artistic output? If I had an easy answer I'd not have written this post. But regardless of value, I know these things are necessary. In the film &lt;a href="https://en.wikipedia.org/wiki/The_Red_Shoes_(1948_film)"&gt;The Red Shoes&lt;/a&gt;, a dancer is asked why she wants to dance. She replies "Why do you want to live?" - to which the asker says "I don't know exactly why, but I must."&lt;/p&gt;
&lt;p&gt;Her rejoinder - &lt;a href="https://www.youtube.com/watch?v=siHfCV6vuSc&amp;amp;t=1191s"&gt;"That's my answer too."&lt;/a&gt;&lt;/p&gt;</description><author>~gallant</author><pubDate>Thu, 28 Nov 2024 04:04:29 GMT</pubDate><guid isPermaLink="true">https://gallant.dev/posts/whither-poetry/</guid></item><item><title>Old Dads: Not an Old Movie, but it's good those good ol' vibes</title><link>https://olshansky.info/movie/old_dads/</link><description>Olshansky's review of Old Dads</description><author>🦉 olshansky 🦁</author><pubDate>Thu, 28 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/movie/old_dads/</guid></item><item><title>Town Hall #27: Skies</title><link>https://taylor.town/town-hall-0027</link><description>gray skies are okay too</description><author>taylor.town</author><pubDate>Thu, 28 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/town-hall-0027</guid></item><item><title>AI: The Somnium Files - nirvanA Initiative review</title><link>https://burakku.com/blog/ai-the-somnium-files-nirvana-initiative-review/</link><description>&lt;p&gt;&lt;img alt="AI: The Somnium Files - nirvanA Initiative" src="thumbnail.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Back agAIn.&lt;/p&gt;
&lt;p&gt;The Somnium Files are back except this time instead of running around as Date Kaname, you’re now given control of two different protagonists, who split the game’s plot into two halves: the past and the present. The past/present split gives the game a more linear presentation than the first game’s non-linear, wilder branching. Otherwise the gist is the same: run around like crazy and investigate a complex and bizarre murder mystery. Tone of the game is also the same: can take itself seriously but trends towards wackier, light-hearted comedy whenever it can.&lt;/p&gt;
&lt;p&gt;Narratively nirvanA Initiative is good. It’s not as good as the original Somnium Files, but it’s good and enjoyable. If you liked the first story, you'll probably like this one too. Granted, some of the elements like the mastermind is weaker and the game is definitely more abstract, but I wouldn’t say that any part of the game ruins the journey. I also give it credit for not just retreading the track that the original game took, so it doesn’t feel like you’re just doing a replay. The characters in Nirvana Initiative, both returning and new, are still very strong and definitely made me want to keep playing through. While I don’t think that either of the new protagonists is as fun and full of a package as Date was, I still liked both of them.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Mizuki" src="mizuki.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Some bits of the story definitely feel like retcons, but I found the explanations given to make sense in-universe, so it didn’t really bother me. Not that any of them were so major that they’d be worth getting upset over, even if they left them completely unexplained. I also think that the additional backstory that they gave to some of the characters really rounded up some of the mysteries of the first game, so I’m a-okay with them.&lt;/p&gt;
&lt;p&gt;There are definitely flaws in the storytelling though. Some bits were seemingly brought up to set up another scene without really ever being explained later on. Uchikoshi could’ve definitely done an additional pass through the story to make sure he didn’t introduce something only to drop it in the second scene. There were also some red herrings that seemed to not matter in any way, and felt like they only existed to fuck with you without any explanation why they were there in the first place. Could be that there was some information to explain away these red herrings, but with the game being almost 30 hours long and clues being spread quite thinly throughout that duration, it’s not easy to ascertain that there wasn’t a throwaway line somewhere that explained it. And even if there were, I’d still consider it a demerit that it wasn’t brought up in a bigger way when they were showcased as an indication of some kind of a conspiracy.&lt;/p&gt;
&lt;p&gt;Then there’s the big twist. I wasn’t sure how to feel about the big twist at first, since it initially really felt like an asspull. However, given a bit of time and letting the game explain itself, it did start to mesh together to a point where I appreciated it more. There were also at least some hinting toward it, although I think there could’ve been more (unless I was just stupid and missed all of it), as the indication of the twist really just manifested as a sensation of something being really wrong. I imagine that basically no one managed to guess it correctly during the story. The big twist is also quite different from the big twist of the first game, so it doesn’t feel like just a repeat of the first game. So different that it’s probably why a lot of people seem to be turned away from the game entirely. You either like it or you hate it, whereas I think almost everyone liked the original game’s big twist.&lt;/p&gt;
&lt;p&gt;The gameplay is almost the same as with the previous instalment, with most of the time being spent in the visual novel investigatory sections with Somniums in between. The biggest core change is that they’ve replaced the evidence-showing interrogation sections with virtual reality puzzle-solving sections, where you explore the scene of the crime in a 3D space and create a reconstruction of the events. Sorta like a mini-Somnium with a pop quiz at the end. I quite liked these VR sections, but I would’ve liked to have some of the evidence sections of the previous game too. Would’ve made it feel a bit more like you were doing proper detective work.&lt;/p&gt;
&lt;p&gt;&lt;img alt="VR investigation" src="wet.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Quick-time events are still very much present, much to many people’s chagrin I imagine. They shouldn’t take anyone by surprise at this point, given the amount of Pavlovian conditioning the series has done by playing the same battle music every time you’re expected to hit random buttons in rhythm. I don’t exactly hate the QTE sections, but they’re hardly the best and brightest gameplay available. At best they're a change of pace from the point-and-clicking adventuring. At least the battle choreography for these sections is fun to watch.&lt;/p&gt;
&lt;p&gt;While not drastically different, the Somnium sections seem like a step up from the first game. Not only do the actions feel less like taking stabs in the dark, but there’s also more actual puzzle-solving. Less of “I guessed it right” and more of “I solved the riddle.” Granted, there is still some amount of brute-forcing that you can do and I managed to send one Somnium by guessing the final answer, as my guess was just the correct size to fit the answer box. Still, the amount of variance in the Somnium gameplay mechanics really does make them feel more thought out than previously.&lt;/p&gt;
&lt;p&gt;However, I’m not sure if there really was a need for so many Somnium segments, since a few of them didn’t really move the plot forwards all that much. Maybe they just wanted to break up the visual novel sections a bit more?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Somnium" src="somnium.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;On the technical side, nirvanA Initiative is not much different from the first game. I could’ve done with better anti-aliasing and improved support for using a controller and keyboard simultaneously. The Somnium transition video is also compressed to hell and back. Surely there was no need to squeeze down the game’s install size this much? Thankfully this time they managed to make the ending movie play in the correct language, so there’s been at least some progress. A passing grade.&lt;/p&gt;
&lt;p&gt;Localisation side is good but not perfect. I played through with the Japanese audio track (as one should) and some of the translated jokes really did not land as well in English as they did in the original script. I understand that some of the puns are definitely somewhere between hard and impossible to get right, but there were definitely places where there was a way to deliver the joke better. Some of it might’ve been due to trying to gear the game towards western audiences, with the expectation that the average player wouldn’t really get any references.&lt;/p&gt;
&lt;p&gt;All in all, while not quite as brilliant as the first game, nirvanA Initiative is still a fun and interesting sequel, and one that I would recommend to anyone who liked the first game. If you didn't like it, you won't like this one either. And if you haven't played the original? Go play it instead. It’s a sequel, what’d you expect?&lt;/p&gt;
&lt;p&gt;If they ever make a third game, aI’ll be sure to cobble up that one as well. A day one purchase, most likely.&lt;/p&gt;</description><author>ブラック</author><pubDate>Thu, 28 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://burakku.com/blog/ai-the-somnium-files-nirvana-initiative-review/</guid></item><item><title>1 million page views</title><link>http://notes.eatonphil.com/2024-11-28-1-million-views.html</link><description>&lt;p&gt;I was delighted to notice this morning that this site has recently
passed 1M page views. And since Murat
&lt;a href="https://muratbuffalo.blogspot.com/2017/02/1-million-pageviews.html"&gt;wrote&lt;/a&gt;
about his 1M page view accomplishment at the time, I felt compelled to
now too.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/1m-page-views.png" src="/assets/1m-page-views.png" /&gt;&lt;/p&gt;
&lt;p&gt;I started regularly blogging in 2018. For some reason I decided to
write a blog post every month. And while I have definitely skipped a
month or two here or there, on average I've written 2 posts per month.&lt;/p&gt;
&lt;h3 id="tooling"&gt;Tooling&lt;/h3&gt;&lt;p&gt;Since at least 2018 this site has been built with a static site
generator. I might have used a 3rd-party generator at one point, but
for as long as I can remember most of this site has been built with a
&lt;a href="https://github.com/eatonphil/eatonphil.com/blob/main/notes/scripts/build.py"&gt;little Python
script&lt;/a&gt;
I wrote.&lt;/p&gt;
&lt;p&gt;I used to get so pissed when static site generators would pointlessly
change their APIs and I'd have to make pointless changes. I have not
had to make any significant changes to my build code in many years.&lt;/p&gt;
&lt;p&gt;I hosted the site itself on GitHub Pages for many years. But I wanted
more flexibility with subdomains (ultimately not something I liked)
and the ability to view server-side logs (ultimately not something I
ever do).&lt;/p&gt;
&lt;p&gt;I think this site is hosted on an OVH machine now. But at this point
it is inertia keeping me there. If you have no strong feelings
otherwise, GitHub Pages is perfect.&lt;/p&gt;
&lt;p&gt;I used to use Google Analytics but then they shut down the old
version. The new version was incredibly confusing to use. I could not
find some very basic information. So I moved to Fathom which has been
great.&lt;/p&gt;
&lt;p&gt;I used to track all subscribers in a Google Form and bcc them but this
became untenable eventually after 1000 subscribers due to GMail rate
limits. I currently use MailerLite for subscriptions and sending email
about new posts. But this is an absolutely terrible service. They
proxy all links behind a domain that adblockers hate and they also
visually shorten the URL so you can't copy the text of the URL.&lt;/p&gt;
&lt;p&gt;I just want a service that has a hosted form for collecting
subscribers and a &lt;code&gt;&amp;lt;textarea&amp;gt;&lt;/code&gt; that lets me dump raw HTML and send
that as an email to my subscribers. No branding, no watermarks, no
link proxying. This apparently doesn't exist. I am too lazy to
figure out Amazon SES so I stick with MailerLite for now.&lt;/p&gt;
&lt;h3 id="evolution"&gt;Evolution&lt;/h3&gt;&lt;p&gt;In the beginning I talked about little interpreters in JavaScript,
about programming languages, about Scheme. I was into functional
programming. Over time I moved into little emulators and bytecode
VMs. And for the last four years I became obsessed with databases and
distributed systems.&lt;/p&gt;
&lt;p&gt;I have almost always written about little projects to teach myself a
concept. Writing a &lt;a href="https://notes.eatonphil.com/lua-in-rust.html"&gt;bytecode VM in
Rust&lt;/a&gt;, &lt;a href="https://notes.eatonphil.com/emulating-amd64-starting-with-elf.html"&gt;emulating a
subset of x86 in
Go&lt;/a&gt;,
&lt;a href="https://notes.eatonphil.com/2023-05-25-raft.html"&gt;implementing Raft in
Go&lt;/a&gt;, &lt;a href="https://notes.eatonphil.com/2024-05-16-mvcc.html"&gt;implementing
MVCC isolation levels in
Go&lt;/a&gt;, and so on.&lt;/p&gt;
&lt;p&gt;So many times when I tried to learn a concept I would find blog posts
with only partial code. The post would link to a GitHub repo that, by
the time I got to the post, had evolved significantly beyond what was
described in the post. The repo code had by then become too complex
for me to follow. So I was motivated to write minimal implementations
and walk through the code in its entirety.&lt;/p&gt;
&lt;div class="note"&gt;
  Even today there is not a single post on implementing TCP/IP from
  scratch that walks through entirely working code. (Please, someone
  write this.)
&lt;/div&gt;&lt;p&gt;I have also had a blast writing survey posts such as &lt;a href="https://notes.eatonphil.com/2023-09-21-how-do-databases-execute-expressions.html"&gt;how various
databases execute
expressions&lt;/a&gt;,
&lt;a href="https://notes.eatonphil.com/javascript-implementations.html"&gt;analyzing non-V8 JavaScript
implementations&lt;/a&gt;,
&lt;a href="https://notes.eatonphil.com/parser-generators-vs-handwritten-parsers-survey-2021.html"&gt;how various programming language implementations parse
code&lt;/a&gt;,
and &lt;a href="https://notes.eatonphil.com/whats-the-big-deal-about-key-value-databases.html"&gt;how various database systems build on top of key-value
databases&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The last two posts have even each been cited in a research paper
(&lt;a href="https://arxiv.org/pdf/2208.08235"&gt;here&lt;/a&gt; and
&lt;a href="https://www.usenix.org/system/files/atc23-kaufman.pdf"&gt;here&lt;/a&gt;).&lt;/p&gt;
&lt;h3 id="editing"&gt;Editing&lt;/h3&gt;&lt;p&gt;In terms of quality, my single greatest trick is to read the post out
loud. Multiple times. Notice parts that are awkward or unclear and
rewrite them.&lt;/p&gt;
&lt;p&gt;My second greatest trick is to ask friends for review. Some posts like
&lt;a href="https://notes.eatonphil.com/2024-02-08-an-intuition-for-distributed-consensus-in-oltp-systems.html"&gt;an intuition for distributed
consensus&lt;/a&gt;
and &lt;a href="https://notes.eatonphil.com/2024-07-01-a-write-ahead-log-is-not-a-universal-part-of-durability.html"&gt;a write-ahead log is not a universal part of
durability&lt;/a&gt;
would simply not have been correct or credible without my fantastic
reviewers. And I'm proud to have &lt;a href="https://eatonphil.com/editor.html"&gt;played that
part&lt;/a&gt; a few times in turn.&lt;/p&gt;
&lt;p&gt;We also have a fantastic #writing-and-drafts channel on the &lt;a href="https://eatonphil.com/discord.html"&gt;Software
Internals Discord&lt;/a&gt; where folks
(myself occasionally included) come for post review.&lt;/p&gt;
&lt;h3 id="context"&gt;Context&lt;/h3&gt;&lt;p&gt;I've lost count of the total number of times that these posts have
been on the front page of Hacker News or that a tweet announcing a
post has reached triple digits likes. I think I've had 9 posts on the
front of HN this year. I do know that my single best year for HN was
12 months between 2022-2023 where 20 of my posts or projects were on
the front page.&lt;/p&gt;
&lt;p&gt;Every time a post does well there's a part of me that worries that
I've peaked. But the way to deal with this has been to ignore that
little voice and to just keep learning new things. I haven't stopped
finding things confusing yet, and &lt;a href="https://notes.eatonphil.com/2024-06-14-confusion-is-a-muse.html"&gt;confusion is a phenomenal
muse&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And also to, like, go out and meet friends for dinner,
&lt;a href="https://nycsystems.xyz/"&gt;run&lt;/a&gt;
&lt;a href="https://eatonphil.com/nyc-systems-coffee-club.html"&gt;meetups&lt;/a&gt;, run &lt;a href="https://eatonphil.com/bookclub.html"&gt;book clubs&lt;/a&gt;,
&lt;a href="https://eatonphil.com/chat.html"&gt;chat&lt;/a&gt; with you fascinating internet
strangers, play volleyball, and so on.&lt;/p&gt;
&lt;p&gt;It's always been about &lt;a href="https://notes.eatonphil.com/2024-08-24-obsession.html"&gt;cultivating healthy
obsessions&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="benediction"&gt;Benediction&lt;/h3&gt;&lt;p&gt;In parting, I'll remind you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://notes.eatonphil.com/is-it-worth-writing-about.html"&gt;It is definitely worth writing about&lt;/a&gt;,
whatever "it" is&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/eatonphil/status/1854965419745972394"&gt;You're not writing enough&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;And &lt;a href="https://eatonphil.com/call-for-posts.html"&gt;some ideas for posts I want to hear about if you write about them&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a little reflection on writing after noticing I passed 1M page views this morning.&lt;a href="https://t.co/eIlMDVHNht"&gt;https://t.co/eIlMDVHNht&lt;/a&gt; &lt;a href="https://t.co/EKSiiDUz5G"&gt;pic.twitter.com/EKSiiDUz5G&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1862174926104318407?ref_src=twsrc%5Etfw"&gt;November 28, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 28 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-11-28-1-million-views.html</guid></item><item><title>Fly.io Postgres failover fix</title><link>https://adriano.fyi/posts/fly-dot-io-postgres-failover-fix/</link><description>&lt;h1 id="flyio-postgres-failover-fix-flyctl-pg-failover"&gt;Fly.io Postgres failover fix (&lt;code&gt;flyctl pg failover&lt;/code&gt;)&lt;/h1&gt;
&lt;p&gt;This is a note to myself, meant to be succinct and helpful. I&amp;rsquo;m sharing it publicly to save others time.&lt;/p&gt;
&lt;p&gt;Most of the time Fly.io works as I expect it to, but occasionally there are edge cases that lack documentation, public announcements, or both.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s possible that at some point Fly.io announced a breaking change and I missed it, but the behavior I observed deserves more than an announcement or silently released documentation.&lt;/p&gt;
&lt;p&gt;Today, I wanted to perform a manual leader failover to one of my cluster&amp;rsquo;s followers, but it quickly failed in a way that wasn&amp;rsquo;t immediately clear.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The problem&lt;/h2&gt;
&lt;p&gt;Performing &lt;code&gt;flyctl pg failover&lt;/code&gt; can fail for opaque reasons because the underlying error is eaten.&lt;/p&gt;
&lt;p&gt;Example&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;$ flyctl pg failover -a &amp;lt;APP&amp;gt; --debug
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Performing a failover
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Connecting to fdaa:9:1d32:a7b:94:1aeb:7b94:2... complete
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Stopping current leader...  328725ec309d85
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Starting new leader
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Promoting new leader...  e784126feee248
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Connecting to fdaa:9:1d32:a7b:94:1aeb:7b94:2... complete
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;WARNING: unable to connect to remote host &lt;span style="color: #e6db74;"&gt;"fdaa:9:1d32:a7b:e:e63c:7516:2"&lt;/span&gt; via SSH
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;WARNING: unable to connect to remote host &lt;span style="color: #e6db74;"&gt;"fdaa:9:1d32:a7b:2b5:4c5e:f3b8:2"&lt;/span&gt; via SSH
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now this isn&amp;rsquo;t entirely opaque &amp;ndash; we can see that &lt;em&gt;something&lt;/em&gt; is having a problem connecting to two cluster nodes via SSH. But what is trying to connect via SSH? My local machine, or one of the replicas in the Postgres cluster?&lt;/p&gt;
&lt;p&gt;If you aren&amp;rsquo;t aware of how Fly Postgres clustering works, under the hood it&amp;rsquo;s simply &lt;a href="https://github.com/EnterpriseDB/repmgr"&gt;repmgr&lt;/a&gt;, which under &lt;em&gt;its&lt;/em&gt; hood uses &lt;a href="https://www.repmgr.org/docs/4.4/performing-switchover.html"&gt;passwordless SSH sessions&lt;/a&gt; to orchestrate changes with cluster members.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;fly pg failover&lt;/code&gt; executes &lt;code&gt;repmgr standby switchover --siblings-follow&lt;/code&gt; (amongst other things) on one of the cluster&amp;rsquo;s follower nodes, which takes over the &lt;code&gt;primary&lt;/code&gt; role in the cluster.&lt;/p&gt;
&lt;p&gt;So the errors above &amp;ndash; those are coming from a &lt;em&gt;follower&lt;/em&gt; cluster node attempting connections to other cluster nodes. Failover must fail because without SSH sessions to other cluster nodes, &lt;code&gt;repmgr&lt;/code&gt; cannot orchestrate any changes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Aside: &lt;code&gt;fly pg failover&lt;/code&gt; should absolutely accept repmgr&amp;rsquo;s &lt;code&gt;--dry-run&lt;/code&gt; switch for performing dry runs. Currently, it does not. Typically &lt;code&gt;fly pg failover&lt;/code&gt; stops the leader machine to make way for the new leader. A &lt;code&gt;--dry-run&lt;/code&gt; switch should prevent any such service disruption, just as repmgr&amp;rsquo;s dry run behavior prevents service disruption.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Unfortunately, &lt;code&gt;repmgr&lt;/code&gt; can eat errors from &lt;code&gt;ssh&lt;/code&gt; and won&amp;rsquo;t show you exactly &lt;em&gt;why&lt;/em&gt; some connections to remote hosts aren&amp;rsquo;t possible.&lt;/p&gt;
&lt;p&gt;By using SSH directly (&lt;code&gt;ssh postgres@fdaa:9:1d32:a7b:e:e63c:7516:2&lt;/code&gt;) I quickly spotted the underlying error.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;postgres@fdaa:9:1d32:a7b:e:e63c:7516:2: Permission denied (publickey).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point, the problem was clearly a SSH public key problem, but I wasn&amp;rsquo;t aware of the below command that&amp;rsquo;ll quickly fix your cluster members up with working ssh keys.&lt;/p&gt;
&lt;h2 id="the-fix"&gt;The fix&lt;/h2&gt;
&lt;p&gt;To fix this issue, re-distribute SSH public keys throughout the cluster with&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;fly pg renew-certs -a &amp;lt;APP&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Your SSH certificate(s) have been renewed are set to expire in 36525 day(s)
Run fly deploy --app &amp;lt;APP&amp;gt; --image docker-hub-mirror.fly.io/flyio/postgres-flex:15.8@sha256:5016ffb34e66eca43d4f9ef2f898c166257bd28bd5095c41d049a5e3be15caf5 to apply the changes!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Don&amp;rsquo;t forget to re-deploy your app after renewing certificates.&lt;/p&gt;</description><author>Adriano Caloiaro's personal blog</author><pubDate>Thu, 28 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://adriano.fyi/posts/fly-dot-io-postgres-failover-fix/</guid></item><item><title>Hetzner raises prices while significantly lowering bandwidth (US)</title><link>https://adriano.fyi/posts/hetzner-raises-prices-while-significantly-lowering-bandwidth-in-us/</link><description>&lt;h2 id="hetzner-raises-prices"&gt;Hetzner raises prices&lt;/h2&gt;
&lt;p&gt;This morning I received an email from Hetzner stating that they are raising prices in the US while significantly reducing bandwidth.&lt;/p&gt;
&lt;p&gt;The largest price percentage increase is &lt;strong&gt;27.52%&lt;/strong&gt; for &lt;strong&gt;CPX21&lt;/strong&gt; servers, and the smallest is &lt;strong&gt;4.17%&lt;/strong&gt; for &lt;strong&gt;CX3+&lt;/strong&gt; servers.&lt;/p&gt;
&lt;p&gt;Bandwidth allotments are decreasing on average, across all products, &lt;strong&gt;88.19%&lt;/strong&gt; from previous allotments.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been a big fan of Hetzner. Unfortunately they&amp;rsquo;ve made a feeble attempt to dress this change up in the name of &amp;ldquo;fairness&amp;rdquo;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;With the new tariff structure, we want to make conditions for our customers around the world as fair as possible &amp;hellip; Until this change, customers who have used fewer resources have covered the costs, in a way, for other customers who have used much more resources. We want to make things more balanced. The new prices will give our customers the best possible price for the resources they use.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Hetzner&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;I&amp;rsquo;d love to see a better explanation for why prices are increasing &lt;em&gt;so much&lt;/em&gt; in exchange for such a significant drop in bandwidth allotments. A lot of Hetzner customers cite generous bandwidth allotments as one of the reasons they&amp;rsquo;ve chosen Hetzner as a provider. As far as I can tell, that generosity has evaporated in the great US of A.&lt;/p&gt;
&lt;p&gt;And the fact that this was announced on Thanksgiving Day, a major holiday in the US, certainly deserves a dishonorable mention.&lt;/p&gt;
&lt;h2 id="update"&gt;Update&lt;/h2&gt;
&lt;p&gt;Now that I&amp;rsquo;ve looked at my invoice, Hetzner&amp;rsquo;s price change and justification is even more frustrating.&lt;/p&gt;
&lt;p&gt;
&lt;input hidden="hidden" id="zoomCheck-ad53e" type="checkbox" /&gt;
&lt;label for="zoomCheck-ad53e"&gt;
  &lt;img alt="hetzner invoice" class="zoomCheck" src="https://adriano.fyi/img/hetzner-invoice.png" title="hetzner invoice" /&gt;
&lt;/label&gt;
&lt;/p&gt;
&lt;p&gt;I personally use &lt;code&gt;0TB&lt;/code&gt; per month across 6 &lt;code&gt;CPX21&lt;/code&gt; servers (I know I&amp;rsquo;m over-provisioned; that&amp;rsquo;s not the point). The point is I&amp;rsquo;ve been paying &lt;code&gt;€42.30 + €1.00/mo&lt;/code&gt; for &lt;code&gt;20TB&lt;/code&gt; of bandwidth, of which I&amp;rsquo;ve used zero on average. What Hetzner&amp;rsquo;s statement tells me is that even at that price, I was subsidizing some heavy users. And to thank me for it, I can now pay &lt;code&gt;€53.94/mo&lt;/code&gt; in exchange for &lt;code&gt;12TB&lt;/code&gt;, for fairness.&lt;/p&gt;
&lt;h2 id="new-price-and-bandwidth-table"&gt;New price and bandwidth table&lt;/h2&gt;
&lt;p&gt;From the updated bandwidth and pricing table provided in Hetzner&amp;rsquo;s email, here are the price changes for CCX and CPX servers in Ashburn and Hillsboro.&lt;/p&gt;


&lt;table border="1" class="dataframe"&gt;
  &lt;thead&gt;
    &lt;tr style="text-align: right;"&gt;
      &lt;th&gt;Product&lt;/th&gt;
      &lt;th&gt;Old(€)&lt;/th&gt;
      &lt;th&gt;New(€)&lt;/th&gt;
      &lt;th&gt;Increase(%)&lt;/th&gt;
      &lt;th&gt;Old Traffic (TB)&lt;/th&gt;
      &lt;th&gt;New Traffic (TB)&lt;/th&gt;
      &lt;th&gt;Traffic Decrease (%)&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;CPX11&lt;/td&gt;
      &lt;td&gt;3.85&lt;/td&gt;
      &lt;td&gt;4.49&lt;/td&gt;
      &lt;td&gt;16.62%&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;95.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPX21&lt;/td&gt;
      &lt;td&gt;7.05&lt;/td&gt;
      &lt;td&gt;8.99&lt;/td&gt;
      &lt;td&gt;27.52%&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;90.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPX31&lt;/td&gt;
      &lt;td&gt;13.10&lt;/td&gt;
      &lt;td&gt;15.99&lt;/td&gt;
      &lt;td&gt;22.06%&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;85.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPX41&lt;/td&gt;
      &lt;td&gt;24.70&lt;/td&gt;
      &lt;td&gt;29.99&lt;/td&gt;
      &lt;td&gt;21.42%&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;80.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CPX51&lt;/td&gt;
      &lt;td&gt;54.40&lt;/td&gt;
      &lt;td&gt;59.99&lt;/td&gt;
      &lt;td&gt;10.28%&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;5&lt;/td&gt;
      &lt;td&gt;75.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CCX13&lt;/td&gt;
      &lt;td&gt;11.99&lt;/td&gt;
      &lt;td&gt;12.99&lt;/td&gt;
      &lt;td&gt;8.34%&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;95.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CCX23&lt;/td&gt;
      &lt;td&gt;23.99&lt;/td&gt;
      &lt;td&gt;25.99&lt;/td&gt;
      &lt;td&gt;8.34%&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;90.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CCX33&lt;/td&gt;
      &lt;td&gt;47.99&lt;/td&gt;
      &lt;td&gt;49.99&lt;/td&gt;
      &lt;td&gt;4.17%&lt;/td&gt;
      &lt;td&gt;30&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;90.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CCX43&lt;/td&gt;
      &lt;td&gt;95.99&lt;/td&gt;
      &lt;td&gt;99.99&lt;/td&gt;
      &lt;td&gt;4.17%&lt;/td&gt;
      &lt;td&gt;40&lt;/td&gt;
      &lt;td&gt;4&lt;/td&gt;
      &lt;td&gt;90.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CCX53&lt;/td&gt;
      &lt;td&gt;191.99&lt;/td&gt;
      &lt;td&gt;199.99&lt;/td&gt;
      &lt;td&gt;4.17%&lt;/td&gt;
      &lt;td&gt;50&lt;/td&gt;
      &lt;td&gt;6&lt;/td&gt;
      &lt;td&gt;88.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;CCX63&lt;/td&gt;
      &lt;td&gt;287.99&lt;/td&gt;
      &lt;td&gt;299.99&lt;/td&gt;
      &lt;td&gt;4.17%&lt;/td&gt;
      &lt;td&gt;60&lt;/td&gt;
      &lt;td&gt;8&lt;/td&gt;
      &lt;td&gt;86.666667&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LB11&lt;/td&gt;
      &lt;td&gt;5.39&lt;/td&gt;
      &lt;td&gt;5.39&lt;/td&gt;
      &lt;td&gt;0.00%&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;1&lt;/td&gt;
      &lt;td&gt;95.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LB21&lt;/td&gt;
      &lt;td&gt;16.40&lt;/td&gt;
      &lt;td&gt;16.40&lt;/td&gt;
      &lt;td&gt;0.00%&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;2&lt;/td&gt;
      &lt;td&gt;90.000000&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;LB31&lt;/td&gt;
      &lt;td&gt;32.90&lt;/td&gt;
      &lt;td&gt;32.90&lt;/td&gt;
      &lt;td&gt;0.00%&lt;/td&gt;
      &lt;td&gt;20&lt;/td&gt;
      &lt;td&gt;3&lt;/td&gt;
      &lt;td&gt;85.000000&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;


&lt;h2 id="hetzners-email"&gt;Hetzner&amp;rsquo;s email&lt;/h2&gt;


&lt;table align="center" border="0" cellpadding="0" cellspacing="0" height="100%" width="100%"&gt;
  &lt;tr&gt;
    &lt;td height="100%" width="100%"&gt;
      &lt;table align="center" border="0" cellpadding="0" cellspacing="0"&gt;
        &lt;tr&gt;
          &lt;td class="logo"&gt;&lt;img alt="Hetzner Online 
GmbH" src="https://cdn.hetzner.com/assets/hetzner-logo-300.png" width="200" /&gt;&lt;/td&gt;&lt;/tr&gt;
        &lt;tr&gt;
          &lt;td style="padding-bottom: 20px;"&gt;&lt;p class="client-reference"&gt;Your client number: XXXXXXXXXX&lt;/p&gt;

&lt;p class="salutation"&gt;Dear Adriano Caloiaro&lt;/p&gt;

&lt;p&gt;We are writing to inform you about important changes to the tariff 
structure of our Cloud servers (CCX and CPX lines) and our Load balancers at 
our US locations in Ashburn and Hillsboro.&lt;/p&gt;

&lt;h4&gt;What will change?&lt;/h4&gt;

&lt;p&gt;Starting on 1 December 2024, 01:00 am CET, we will begin charging new 
prices for newly-created Cloud servers and introduce new amounts for included 
traffic for Cloud Servers and Load balancers at the US locations in Ashburn 
(ASH) and Hillsboro (HIL). This also applies to existing Cloud servers and 
Load balancers that are switched to a different tariff using the 
“Rescale” function. For any existing Cloud servers and Load balancers you 
have at these locations, the new prices and the new amounts for included 
traffic will apply later, starting on 1 February 2025, 01:00 am CET. The 
price for traffic overage will remain unchanged in the new price 
structure.&lt;/p&gt;

&lt;p&gt;What are the new prices and amounts of included traffic?&lt;/p&gt;

&lt;p&gt;Below, you can see a list of the old and new prices and the included 
traffic.
&lt;table&gt;
&lt;tr&gt;&lt;td&gt;&lt;b&gt;Product&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;Old price&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;New 
price&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;Old included traffic&lt;/b&gt;&lt;/td&gt;&lt;td&gt;&lt;b&gt;New included 
traffic&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CPX11&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;3.85&lt;/td&gt;&lt;td&gt;&amp;euro; 
4.49&lt;/td&gt;&lt;td&gt;20&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;1&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CPX21&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;7.05&lt;/td&gt;&lt;td&gt;&amp;euro; 
8.99&lt;/td&gt;&lt;td&gt;20&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;2&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CPX31&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;13.10&lt;/td&gt;&lt;td&gt;&amp;euro; 
15.99&lt;/td&gt;&lt;td&gt;20&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;3&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CPX41&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;24.70&lt;/td&gt;&lt;td&gt;&amp;euro; 
29.99&lt;/td&gt;&lt;td&gt;20&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;4&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CPX51&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;54.40&lt;/td&gt;&lt;td&gt;&amp;euro; 
59.99&lt;/td&gt;&lt;td&gt;20&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;5&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CCX13&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;11.99&lt;/td&gt;&lt;td&gt;&amp;euro; 
12.99&lt;/td&gt;&lt;td&gt;20&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;1&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CCX23&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;23.99&lt;/td&gt;&lt;td&gt;&amp;euro; 
25.99&lt;/td&gt;&lt;td&gt;20&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;2&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CCX33&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;47.99&lt;/td&gt;&lt;td&gt;&amp;euro; 
49.99&lt;/td&gt;&lt;td&gt;30&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;3&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CCX43&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;95.99&lt;/td&gt;&lt;td&gt;&amp;euro; 
99.99&lt;/td&gt;&lt;td&gt;40&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;4&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CCX53&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;191.99&lt;/td&gt;&lt;td&gt;&amp;euro; 
199.99&lt;/td&gt;&lt;td&gt;50&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;6&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;CCX63&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;287.99&lt;/td&gt;&lt;td&gt;&amp;euro; 
299.99&lt;/td&gt;&lt;td&gt;60&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;8&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LB11&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;5.39&lt;/td&gt;&lt;td&gt;unchanged&lt;/td&gt;&lt;td&gt;20&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;1&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LB21&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;16.40&lt;/td&gt;&lt;td&gt;unchanged&lt;/td&gt;&lt;td&gt;20&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;2&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;LB31&lt;/td&gt;&lt;td&gt;&amp;euro;&amp;nbsp;32.90&lt;/td&gt;&lt;td&gt;unchanged&lt;/td&gt;&lt;td&gt;20&amp;nbsp;TB&lt;/td&gt;&lt;td&gt;3&amp;nbsp;TB&lt;/td&gt;&lt;/tr&gt;
&lt;/table&gt;
All monthly prices are excl. VAT and excl. IPv4 addresses.&lt;/p&gt;

&lt;h4&gt;Why are we making these changes?&lt;/h4&gt;

&lt;p&gt;With the new tariff structure, we want to make conditions for our 
customers around the world as fair as possible. To do that, we will calculate 
our prices based on local conditions in Europe, Singapore, and the USA. Until 
this change, customers who have used fewer resources have covered the costs, 
in a way, for other customers who have used much more resources. We want to 
make things more balanced. The new prices will give our customers the best 
possible price for the resources they use.&lt;/p&gt;

&lt;h4&gt;What else do I need to know?&lt;/h4&gt;

&lt;p&gt;Displayed prices and included traffic on Cloud Console&lt;/p&gt;

&lt;p&gt;For technical reasons, we will display the new prices and amounts of 
included traffic on the Cloud server and Load balancer detail pages on Cloud 
Console starting on 1 December 2024. This will also be true for your existing 
Cloud servers and Load balancers. However, the invoice will still be issued 
according to the previous conditions. A corresponding note will be displayed 
for your existing affected products. On the invoice preview on the 
“Usage” page, you will see the correct existing price for your existing 
servers.&lt;/p&gt;

&lt;p&gt;Displayed prices on the Cloud API&lt;/p&gt;

&lt;p&gt;In the transition time period between 1 December 2024 and 1 February 2025, 
the Cloud API will already return the new prices and included traffic. This 
will also be true for your existing Cloud servers and Load balancers. But 
this will only affect the display here. The invoice preview will display the 
correct existing price. And of course, your invoice itself will be for the 
correct existing price.&lt;/p&gt;

&lt;p&gt;Traffic warning&lt;/p&gt;

&lt;p&gt;You may see early traffic warnings during the transition time period 
starting on 1 December 2024 for your existing Cloud servers and Load 
balancers. Please note that the previous traffic included for existing Cloud 
servers and Load balancers remains valid until 31 January 2025.&lt;/p&gt;

&lt;p&gt;If you do not agree with this adjustment, you can cancel the affected 
products at any time. We ask for your understanding for why we are making 
these changes. We truly believe that we can continue to offer you our Cloud 
Servers and features at the best price-performance ratio despite these 
adjustments.&lt;/p&gt;

&lt;p&gt;We will be happy to answer any questions you may have. Please write us a 
support request using your account on Cloud Console at &lt;a href="https://console.hetzner.cloud/support"&gt;https://console.hetzner.cloud/support&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thank you for your trust and support.&lt;/p&gt;

&lt;p&gt;Kind regards&lt;/p&gt;

&lt;p&gt;Hetzner Online&lt;/p&gt;
  &lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td class="footer"&gt;
    Hetzner Online GmbH, Industriestr. 25, 91710 Gunzenhausen, Germany&lt;br /&gt;
    CEO: Martin Hetzner, Stephan Konvickova, Günther Müller&lt;br /&gt;Register 
Court: Registergericht Ansbach, HRB 6089,
    VAT Reg. No.: DE812871812
  &lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;
&lt;td class="footer"&gt;
  &lt;a href="https://www.hetzner.com/rechtliches/agb"&gt;Terms and Conditions&lt;/a&gt;  
 •   &lt;a href="https://www.hetzner.com/rechtliches/impressum/"&gt;Legal 
Notice&lt;/a&gt;    •   &lt;a href="https://www.hetzner.com/datenschutzhinweis"&gt;Data 
Privacy&lt;/a&gt;   •   &lt;a href="https://www.hetzner.com/rechtliches/system-policies/"&gt;System 
Policies&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;</description><author>Adriano Caloiaro's personal blog</author><pubDate>Thu, 28 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://adriano.fyi/posts/hetzner-raises-prices-while-significantly-lowering-bandwidth-in-us/</guid></item><item><title>Forsskål symposium 2024: American freedoms and unfreedoms through history and now</title><link>https://liza.io/forssk%C3%A5l-symposium-2024-american-freedoms-and-unfreedoms-through-history-and-now/</link><description>&lt;p&gt;These are my rough notes from the Forsskål symposium 2024, &amp;ldquo;Amerikanska friheter och ofriheter i historia och samtid&amp;rdquo; - American freedoms and unfreedoms in history and now.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Wed, 27 Nov 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/forssk%C3%A5l-symposium-2024-american-freedoms-and-unfreedoms-through-history-and-now/</guid></item><item><title>Thoughts On Moving Debezium to the Commonhaus Foundation</title><link>https://www.morling.dev/blog/thoughts-on-moving-debezium-to-commonhaus-foundation/</link><description>&lt;div class="paragraph"&gt;
&lt;p&gt;If you are following the news around Debezium—​an open-source platform for Change Data Capture (CDC) for a variety of databases—​you may have seen the announcement that the project is in the process of &lt;a href="https://debezium.io/blog/2024/11/04/debezium-moving-to-commonhaus/"&gt;moving to the Commonhaus Foundation&lt;/a&gt;. I think this is excellent news for the Debezium project, its community, and open-source CDC at large. In this post I’d like to share some more context on why I am so excited about this development.&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Wed, 27 Nov 2024 18:25:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/thoughts-on-moving-debezium-to-commonhaus-foundation/</guid></item><item><title>Prosocial Discourse</title><link>https://blog.erlend.sh/prosocial-discourse?pk_campaign=rss-feed</link><description/><author>Open Indie</author><pubDate>Wed, 27 Nov 2024 14:47:56 GMT</pubDate><guid isPermaLink="true">https://blog.erlend.sh/prosocial-discourse?pk_campaign=rss-feed</guid></item><item><title>ob-chatgpt-shell goes multi-model too</title><link>https://xenodium.com/ob-chatgpt-shell-goes-multi-model-too</link><description>&lt;p&gt;A week ago, I announced &lt;a href="https://lmno.lol/alvaro/chatgpt-shell-goes-multi-model"&gt;chatgpt-shell going multi-model&lt;/a&gt;. What I failed to mention is that because &lt;a href="https://github.com/xenodium/ob-chatgpt-shell"&gt;ob-chatgpt-shell&lt;/a&gt; (its &lt;a href="https://orgmode.org/worg/org-contrib/babel/intro.html"&gt;org babel&lt;/a&gt; Emacs cousin) relies on &lt;code&gt;chatgpt-shell&lt;/code&gt;, this &lt;code&gt;babel&lt;/code&gt; package has now gone multi-model also.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/ob-chatgpt-shell-goes-multi-model-too/babel-multi-model.gif" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ob-chatgpt-shell&lt;/code&gt; follows the familiar babel form. To swap models, use the existing &lt;code&gt;:version&lt;/code&gt; param as follows.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-org"&gt;#+begin_src chatgpt-shell :results output :version gpt-4o
  Who built you?
#+end_src

#+RESULTS:
: I was developed by OpenAI, a research organization focused on creating and promoting friendly AI for the benefit of all humanity.

#+begin_src chatgpt-shell :results output :version claude-3-5-sonnet-20240620
  Who built you?
#+end_src

#+RESULTS:
: I was created by Anthropic.

#+begin_src chatgpt-shell :results output :version qwen2.5-coder
  Who built you?
#+end_src

#+RESULTS:
: I was built by Alibaba Cloud. How can I assist you today?

#+begin_src chatgpt-shell :results output :version gemini-1.5-pro-latest
  Who built you?
#+end_src

#+RESULTS:
: I was built by Google.  More specifically, I'm a large language model, trained by Google.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Keep in mind that &lt;code&gt;:version&lt;/code&gt; depends on &lt;code&gt;chatgpt-shell-models&lt;/code&gt; to resolve its models. You may need to add other models. If you add new ones, consider contributing a &lt;a href="https://github.com/xenodium/chatgpt-shell/pulls"&gt;pull request&lt;/a&gt;, so we all benefit from the addition.&lt;/p&gt;
&lt;h2&gt;Should &lt;a href="https://lmno.lol/alvaro/chatgpt-shell-goes-multi-model#should-chatgpt-shell-rename%3F"&gt;ob-chatgpt-shell&lt;/a&gt; rename?&lt;/h2&gt;
&lt;p&gt;See &lt;a href="https://lmno.lol/alvaro/chatgpt-shell-goes-multi-model#should-chatgpt-shell-rename%3F"&gt;this&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Please report issues&lt;/h2&gt;
&lt;p&gt;In addition to being a fairly new feature, &lt;code&gt;chatgpt-shell&lt;/code&gt; multi-model support required quite a few structural changes. We may still need additional polishing follow-ups. If you encounter any issues &lt;a href="https://github.com/xenodium/ob-chatgpt-shell/issues/new"&gt;please report them&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Make this project sustainable&lt;/h2&gt;
&lt;p&gt;Maintaining, experimenting, implementing feature requests, and supporting open-source packages takes work. Today, chatgpt-shell has roughly &lt;a href="https://melpa.org/#/chatgpt-shell"&gt;21.5K downloads on MELPA&lt;/a&gt; and many untracked elsewhere. If you're one of the happy users, &lt;a href="https://github.com/sponsors/xenodium"&gt;consider sponsoring the project&lt;/a&gt;. If you see potential, help &lt;a href="https://github.com/sponsors/xenodium"&gt;fuel development by sponsoring&lt;/a&gt; too.&lt;/p&gt;
&lt;p&gt;Perhaps you enjoy some of the content I write about? Find my Emacs posts/tips useful?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://xenodium.com/"&gt;Blog (xenodium.com)&lt;/a&gt; (Web)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lmno.lol/alvaro"&gt;Blog (lmno.lol/alvaro)&lt;/a&gt; (Web)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alternatively, you want a blogging platform that skips the yucky side effects of the modern web?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I'm building &lt;a href="https://lmno.lol"&gt;lmno.lol&lt;/a&gt; (my blog is &lt;a href="https://lmno.lol/alvaro"&gt;there&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maybe you enjoy one of my other projects?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://plainorg.com"&gt;Plain Org&lt;/a&gt; (org mode / iOS)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flathabits.com"&gt;Flat Habits&lt;/a&gt; (org mode / iOS)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/scratch/id1671420139"&gt;Scratch&lt;/a&gt; (org mode / iOS)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/macosrec"&gt;macosrec&lt;/a&gt; (macOS)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/fresh-eyes/id6480411697?mt=12"&gt;Fresh Eyes&lt;/a&gt; (macOS)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/dwim-shell-command"&gt;dwim-shell-command&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/company-org-block"&gt;company-org-block&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/org-block-capf"&gt;org-block-capf&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/ob-swiftui"&gt;ob-swiftui&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/ready-player"&gt;ready-player&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/sqlite-mode-extras"&gt;sqlite-mode-extras&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/ob-chatgpt-shell"&gt;ob-chatgpt-shell&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/dall-e-shell"&gt;dall-e-shell&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/ob-dall-e-shell"&gt;ob-dall-e-shell&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/shell-maker"&gt;shell-maker&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, umm… I'll just leave my GitHub sponsor page &lt;a href="https://github.com/sponsors/xenodium"&gt;here&lt;/a&gt;.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Wed, 27 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/ob-chatgpt-shell-goes-multi-model-too</guid></item><item><title>Stinky Gifts From Your Idea Kitty</title><link>https://taylor.town/idea-kitty</link><description>taxidermy squirrels stuffed with sand and sapphires</description><author>taylor.town</author><pubDate>Wed, 27 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/idea-kitty</guid></item><item><title>The prefers-reduced-motion CSS Media Query</title><link>https://nelson.cloud/the-prefers-reduced-motion-css-media-query/?ref=rss</link><description>Apply CSS styles conditionally when a user enables reduced motion on their device.</description><author>Nelson Figueroa</author><pubDate>Wed, 27 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/the-prefers-reduced-motion-css-media-query/?ref=rss</guid></item><item><title>13x13 Lecture 13 - How it all ends (ett inblick i universums öde)</title><link>https://liza.io/13x13-lecture-13-how-it-all-ends-ett-inblick-i-universums-%C3%B6de/</link><description>&lt;p&gt;Today I attended my virtual morning writing group, did a bit of unpacking, met a friend for lunch, worked from a really old library with a great view for a few hours, and then went to a talk about the end of the universe.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Tue, 26 Nov 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/13x13-lecture-13-how-it-all-ends-ett-inblick-i-universums-%C3%B6de/</guid></item><item><title>Reading books and commenting on them with ChatGPT</title><link>https://nicolaiarocci.com/reading-books-and-commenting-on-them-with-chatgpt/</link><description>&lt;p&gt;I just finished reading Paul Auster&amp;rsquo;s The New York Trilogy&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;. On this occasion, I discovered a new use for ChatGPT and LLMs. ChatGPT and I chatted about the themes, especially the correlations and connections between the three short novels that comprise the volume. It was an alienating and revealing experience. For the first time, I am reasoning about a book with a machine, not a person. Because it knows everything about the text and draws on the shared global knowledge, it can give more satisfaction than most people do (also, it&amp;rsquo;s not easy to find someone around with whom I can talk about all the books I read!) Yes, it is wordy and repetitive, but it can stimulate and enrich my analysis&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Tue, 26 Nov 2024 20:20:01 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/reading-books-and-commenting-on-them-with-chatgpt/</guid></item><item><title>Las Vegas: Maroon 5 concert</title><link>https://digitalnomadder.micro.blog/2024/11/26/las-vegas-maroon.html</link><description>&lt;p&gt;Our third and final trip to Las Vegas for the year was to see Maroon 5.&lt;/p&gt;
&lt;img alt="" height="256" src="https://cdn.uploads.micro.blog/79953/2024/eaad25fede.jpg" width="197" /&gt;
&lt;p&gt;We flew to Las Vegas four days after &lt;a href="https://digitalnomadder.micro.blog/2024/10/20/virgin-voyages-from.html"&gt;returning from our cruise from Miami&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It was a great show.  He was a great singer and performer.  The seats were amazing and he did part of his set right in front of us&lt;/p&gt;
&lt;p&gt;However, I am ready to take a break from Las Vegas after going three times in one year.&lt;/p&gt;
&lt;img alt="" height="600" src="https://cdn.uploads.micro.blog/79953/2024/45350082b3.jpg" width="337" /&gt;</description><author>The Digital Nomad</author><pubDate>Tue, 26 Nov 2024 18:17:56 GMT</pubDate><guid isPermaLink="true">https://digitalnomadder.micro.blog/2024/11/26/las-vegas-maroon.html</guid></item><item><title>Directionally right</title><link>https://www.aswathkrishnan.com/2024/11/directionally-right.html</link><description>Many people lament about Elon Musk's outspoken views on government inefficiency and his plans for DOGE. They point to how government programs and regulations are helpful, and how his estimates for cost savings are overblown. &lt;br /&gt;&lt;br /&gt;They are &lt;i&gt;technically right&lt;/i&gt;, but Elon is &lt;i&gt;directionally right&lt;/i&gt;. It's more likely than not that the government has lots of wasteful spending and unnecessary bureaucracy.&lt;br /&gt;&lt;br /&gt;In closed systems — predictable and well-understood environments — being technically correct can lead to optimal outcomes. Precision matters when variables are limited and controllable, and the consequences are dire. Engineers designing a bridge, for example, must calculate loads and stresses with exactness to ensure safety.&lt;br /&gt;&lt;br /&gt;But most of the world is not a closed or critical system. It is an open, infinite, and inherently chaotic environment. Variables are countless, and conditions change rapidly and unpredictably. In such a world, &lt;a href="https://www.aswathkrishnan.com/2023/12/action-produces-information.html" target="_blank"&gt;you find answers and progress by doing stuff&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Consider how successful tech companies operate. They release minimum viable products, gather user feedback, and refine accordingly. This isn't about being perfect from the start; it's about moving in the right direction and adjusting course as needed. Each action provides new data and insights, informing the next direction and actions.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Some may argue this can be reckless and risky. But trying to be technically and exhaustively right would be crippling, and stagnation is also damaging. There's also a difference between disregarding technical correctness entirely and recognizing when overdoing it is counterproductive. We need enough precision to avoid catastrophic mistakes but enough flexibility to adapt and improve.&lt;br /&gt;&lt;br /&gt;Love him or hate him, you have to wonder how Elon Musk can pull off so much —Tesla, SpaceX, Neuralink, robots, Twitter, politics, and trolling. I think one key factor is his ability to identify the highest-order bits, be directional right, act with urgency and conviction, and iterate.&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Meanwhile, his technically right critics are yet to send anything to orbit :)&amp;nbsp;&lt;/div&gt;&lt;/div&gt;</description><author>Aswath Krishnan</author><pubDate>Tue, 26 Nov 2024 03:30:14 GMT</pubDate><guid isPermaLink="true">https://www.aswathkrishnan.com/2024/11/directionally-right.html</guid></item><item><title>Ecto - Using select_merge for flexible aggregates</title><link>https://blog.andyglassman.com/2024/11/ecto-using-selectmerge-for-dynamic.html</link><description>&lt;h2 style="text-align: left;"&gt;
    &lt;div class="separator" style="clear: both; text-align: center;"&gt;
        &lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQR4t1bFoA_gbf3Snpa69PviRnuqAHWBE9rSFCLBkqere34DFkRPg1aPbSWHN3UMdO6WG8_LwxfZ4waLmYmL5xbxb6iJ9SQvJjwVxDD9R6ZVKWCuk_ybEx8GuSQEZweiwO7otJ-tCXpLnqgAPudl-o1LkbG2XCwNafxPzjm2XLbKnt4PQKoL4pN_PC/s1024/DALL%C2%B7E%202024-10-25%2009.36.52%20-%20A%20cute%20cartoon%20hedgehog%20sitting%20comfortably%20on%20top%20of%20a%20database%20icon%20with%20no%20text.%20The%20hedgehog%20has%20a%20friendly%20expression%20with%20its%20quills%20neatly%20arra.webp" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhQR4t1bFoA_gbf3Snpa69PviRnuqAHWBE9rSFCLBkqere34DFkRPg1aPbSWHN3UMdO6WG8_LwxfZ4waLmYmL5xbxb6iJ9SQvJjwVxDD9R6ZVKWCuk_ybEx8GuSQEZweiwO7otJ-tCXpLnqgAPudl-o1LkbG2XCwNafxPzjm2XLbKnt4PQKoL4pN_PC/s320/DALL%C2%B7E%202024-10-25%2009.36.52%20-%20A%20cute%20cartoon%20hedgehog%20sitting%20comfortably%20on%20top%20of%20a%20database%20icon%20with%20no%20text.%20The%20hedgehog%20has%20a%20friendly%20expression%20with%20its%20quills%20neatly%20arra.webp" width="320" /&gt;&lt;/a&gt;
    &lt;/div&gt;
&lt;/h2&gt;
&lt;h2 style="text-align: left;"&gt;Problem&lt;/h2&gt;
&lt;div&gt;PostgreSQL is a rock solid database.&amp;nbsp;&amp;nbsp;&amp;nbsp;I've been using &lt;a href="https://www.postgresql.org/docs/current/sql-expressions.html#SYNTAX-AGGREGATES" target="_blank"&gt;aggregate expressions&lt;/a&gt; for a number of features over the past few years in various languages.&amp;nbsp; They can be used to build powerful features for your platform.&amp;nbsp; When teamed up with Elixir and Ecto, you get amazing flexibility in your query generation.&amp;nbsp; Typically the data you want to retrieve via a select is fairly
    static, or there are only one or two different versions of the select you need.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="background-color: #2b2b2b; color: #a9b7c6;"&gt;&lt;pre&gt;&lt;br /&gt;&lt;span style="color: #97e649;"&gt; @&lt;/span&gt;doc """&lt;br /&gt;&lt;span style="background-color: #364135;"&gt; Returns an aggregation of user activity&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt; """&lt;br /&gt;&lt;span style="color: #97e649;"&gt; @&lt;/span&gt;spec user_activity_history&lt;span style="color: #e6e649;"&gt;() &lt;/span&gt;&lt;span style="color: #97e649;"&gt;:: &lt;/span&gt;UserActivityHistory&lt;br /&gt; def user_activity_history&lt;span style="color: #e6e649;"&gt;() &lt;/span&gt;&lt;span style="color: #cc7832;"&gt;do&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;   &lt;/span&gt;query &lt;span style="color: #97e649;"&gt;=&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #97e649;"&gt;     &lt;/span&gt;from&lt;span style="color: #e6e649;"&gt;(&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #e6e649;"&gt;       &lt;/span&gt;u &lt;span style="color: #97e649;"&gt;in &lt;/span&gt;User&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;       &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;select&lt;/span&gt;: %UserActivityHistory&lt;span style="color: #48b9e6;"&gt;{&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #48b9e6;"&gt;         &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;captured_at&lt;/span&gt;: fragment&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;"now()"&lt;span style="color: #e6e649;"&gt;)&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;         &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;hour_1&lt;/span&gt;: filter&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;count&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;id&lt;span style="color: #e6e649;"&gt;)&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;fragment&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;"? &amp;gt;= now() - interval '1 hour'"&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;last_active&lt;span style="color: #e6e649;"&gt;))&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;         &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;hour_6&lt;/span&gt;: filter&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;count&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;id&lt;span style="color: #e6e649;"&gt;)&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;fragment&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;"? &amp;gt;= now() - interval '6 hour'"&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;last_active&lt;span style="color: #e6e649;"&gt;))&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;         &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;hour_12&lt;/span&gt;: filter&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;count&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;id&lt;span style="color: #e6e649;"&gt;)&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;fragment&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;"? &amp;gt;= now() - interval '12 hour'"&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;last_active&lt;span style="color: #e6e649;"&gt;))&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;         &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;hour_24&lt;/span&gt;: filter&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;count&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;id&lt;span style="color: #e6e649;"&gt;)&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;fragment&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;"? &amp;gt;= now() - interval '24 hour'"&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;last_active&lt;span style="color: #e6e649;"&gt;))&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;         &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;day_1&lt;/span&gt;: filter&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;count&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;id&lt;span style="color: #e6e649;"&gt;)&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;fragment&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;"? &amp;gt;= now() - interval '1 day'"&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;last_active&lt;span style="color: #e6e649;"&gt;))&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;         &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;day_7&lt;/span&gt;: filter&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;count&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;id&lt;span style="color: #e6e649;"&gt;)&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;fragment&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;"? &amp;gt;= now() - interval '7 day'"&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;last_active&lt;span style="color: #e6e649;"&gt;))&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;         &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;day_30&lt;/span&gt;: filter&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;count&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;id&lt;span style="color: #e6e649;"&gt;)&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;fragment&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;"? &amp;gt;= now() - interval '30 day'"&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;u&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;last_active&lt;span style="color: #e6e649;"&gt;))&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #e6e649;"&gt;       &lt;/span&gt;&lt;span style="color: #48b9e6;"&gt;}&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #48b9e6;"&gt;     &lt;/span&gt;&lt;span style="color: #e6e649;"&gt;)&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #e6e649;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #e6e649;"&gt;   &lt;/span&gt;Repo&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;replica&lt;span style="color: #e6e649;"&gt;()&lt;/span&gt;&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;one&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;query&lt;span style="color: #e6e649;"&gt;)&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt; end&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The above code generates a rollup of user activity, but what if we wanted a custom interval, or only a subset of these intervals?&amp;nbsp; &amp;nbsp;This is a great use case for&amp;nbsp;&amp;nbsp;&lt;a href="https://hexdocs.pm/ecto/Ecto.Query.html#select_merge/3" target="_blank"&gt;Ecto's select_merge&lt;/a&gt;, but I'm not going to dive into this example. Instead I'll show a more advanced feature where select_merge shined.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;
    At &lt;a href="https://sway.dm/info/" target="_blank"&gt;SwayDM&lt;/a&gt;, we have the concept of &lt;b&gt;Members&lt;/b&gt;, &lt;b&gt;Vendors&lt;/b&gt; and &lt;b&gt;Redemptions&lt;/b&gt;. Members perform
    activities on the platform to earn SwayCash (SC). Vendors offer Redemptions for a set amount
    of SwayCash to Members.
&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;Example Redemptions:&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
    &lt;ul style="text-align: left;"&gt;
        &lt;li&gt;20% off a Yeti cooler: (50 SC)&lt;/li&gt;
        &lt;li&gt;One free coffee:&amp;nbsp; (8 SC)&lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;Vendors want to set limits on their redemptions to ensure they are not abused.&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div&gt;&lt;b&gt;Example Redemption Limits:&lt;/b&gt;&lt;/div&gt;
&lt;div&gt;
    &lt;ul style="text-align: left;"&gt;
        &lt;li&gt;20% off a Yeti cooler: (50 SC)&lt;/li&gt;
        &lt;ul&gt;
            &lt;li&gt;A member can only redeem the Yeti cooler deal once&amp;nbsp;over the life of the redemption.&lt;/li&gt;
            &lt;li&gt;The Vendor only wants 5 Members to use this deal per week.&lt;/li&gt;
            &lt;li&gt;The Vendor only wants 100 Members to use this deal overall.&lt;/li&gt;
        &lt;/ul&gt;
        &lt;li&gt;One free coffee drink:&amp;nbsp; (8 SC)&lt;/li&gt;
        &lt;ul&gt;
            &lt;li&gt;A Member can redeem a free coffee once per week.&lt;/li&gt;
            &lt;li&gt;A Member can redeem a free coffee 10 times over the life of the redemption.&lt;/li&gt;
            &lt;li&gt;A Vendor wants a global limit of 50 free coffees.&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/ul&gt;
&lt;/div&gt;
&lt;h3 style="text-align: left;"&gt;Representing Limits&lt;/h3&gt;
&lt;div&gt;
    First, let's take a look at how the limits are represented in the
    database.&amp;nbsp; &amp;nbsp;There is a `redemptions` table, which embeds a list of
    `limits`.
&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;# boilerplate code not included

schema "redemptions" do
	# ... all other redemption fields, such as name, price, etc
    embeds_many :limits, RedemptionLimit, on_replace: :delete
end

embedded_schema do
  field :limit_scope, Ecto.Enum, values: [:global, :user]
  field :limit_type, Ecto.Enum, values: [:count, :rate_limit]
  field :limit, :integer
  field :interval, :integer
  field :unit, Ecto.Enum, values: [:year, :month, :week, :day, :hour, :minute, :second]
  field :end_of_day, :boolean, default: false
end&lt;/code&gt;&lt;/pre&gt;
&lt;h4 style="text-align: left;"&gt;Storing Redemption History&lt;/h4&gt;
&lt;div&gt;We store records of successful redemptions in a `redemptions_history` table.&amp;nbsp; This records the datetime, user,
    and redemption that occurred. The Member is the user type that is redeeming, and the Vendor is the member type that
    offers redemptions.&amp;nbsp;&lt;/div&gt;
&lt;pre&gt;&lt;code&gt;
schema "redemptions_history" do
  belongs_to :redemption, Redemption
  belongs_to :member, Member
  belongs_to :vendor, Vendor

  timestamps(updated_at: false)
end&lt;/code&gt;&lt;/pre&gt;

&lt;h3 style="text-align: left;"&gt;Showing and Evaluating Limits&lt;/h3&gt;
&lt;div&gt;We have two main scenarios where we want to show, and potentially evaluate limits.&amp;nbsp; The first is when displaying available
    redemptions to the user.&amp;nbsp; &amp;nbsp;We want them to see if a redemption they are interested has a limit.&amp;nbsp;
    Typically in this case we do not need to evaluate the limits.
&lt;/div&gt;
&lt;div&gt;&lt;br /&gt;&lt;/div&gt;
&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEgB-yCKCw2B8SjAi6lMyegbax5FW7kQbghCoHi1VeWUhSIgNO_P3b6e4z5pMaWxUAP1V1_TUsPTLr-llzWHszJhhCx8sMrhB-58Qjp6gNIIdP81nHdjILXwbHDlCTLrwzi1viBEbDiXaI7BUOl4ZCdD_8oq0yNuwNFHwgwPlKvA5Z0QnAA3DvH-6lGi" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="230" src="https://blogger.googleusercontent.com/img/a/AVvXsEgB-yCKCw2B8SjAi6lMyegbax5FW7kQbghCoHi1VeWUhSIgNO_P3b6e4z5pMaWxUAP1V1_TUsPTLr-llzWHszJhhCx8sMrhB-58Qjp6gNIIdP81nHdjILXwbHDlCTLrwzi1viBEbDiXaI7BUOl4ZCdD_8oq0yNuwNFHwgwPlKvA5Z0QnAA3DvH-6lGi" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;
&lt;br /&gt;The second is the redemption detail view, where we do want them to see the current state of the global, and user based limits.&lt;div&gt;&lt;br /&gt;
    &lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"&gt;
        &lt;tbody&gt;
        &lt;tr&gt;
            &lt;td style="text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEicPSDd-f23gt50h-Mg7mktrrExMZ_qs-J2uXmZAw8JftEeS3pibhqm6yeOCFt_OjoalIH7iuD0SHS0QkDj61FpD8UER2NLJQ-DdhttkCdgggmoLPVnGsUSEhtvDLDvpsb56UgoT-tagfYwoKAQyXmfhGrdSVutLcm386aWhwBMv9kMHg05DyDUyR-J" style="margin-left: auto; margin-right: auto;"&gt;&lt;img alt="" height="731" src="https://blogger.googleusercontent.com/img/a/AVvXsEicPSDd-f23gt50h-Mg7mktrrExMZ_qs-J2uXmZAw8JftEeS3pibhqm6yeOCFt_OjoalIH7iuD0SHS0QkDj61FpD8UER2NLJQ-DdhttkCdgggmoLPVnGsUSEhtvDLDvpsb56UgoT-tagfYwoKAQyXmfhGrdSVutLcm386aWhwBMv9kMHg05DyDUyR-J=w338-h731" width="338" /&gt;&lt;/a&gt;&lt;/td&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;td class="tr-caption" style="text-align: center;"&gt;Here we can see the member has hit their daily limit, and
                that they have 9 more remaining.
            &lt;/td&gt;
        &lt;/tr&gt;
        &lt;/tbody&gt;
    &lt;/table&gt;
    &lt;br /&gt;
    &lt;div&gt;
        &lt;div&gt;&lt;br /&gt;&lt;/div&gt;
        &lt;h2 style="text-align: left;"&gt;Solutions&lt;/h2&gt;&lt;h4 style="text-align: left;"&gt;Inefficient Solutions&lt;/h4&gt;
        &lt;div&gt;&lt;b&gt;Retrieve History:&amp;nbsp;&lt;/b&gt;One potential solution would be to retrieve all redemption history records
            from the DB, and evaluate the limits in memory.&amp;nbsp; It would be inefficient to retrieve all history
            records every time we want to evaluate a limit, and the more history records that exist for the redemption,
            the slower it will become.&amp;nbsp; To optimize this, we'd probably have to start caching these history
            records, which would be tedious to keep consistent.
        &lt;/div&gt;
        &lt;div&gt;&lt;br /&gt;&lt;/div&gt;
        &lt;div&gt;&lt;b&gt;Query per Limit: &lt;/b&gt;A better solution would be to run one query per limit.&amp;nbsp; Here are two
            examples:
        &lt;/div&gt;
        &lt;pre&gt;&lt;code&gt;
defmodule RedemptionLimits do

  @doc """
  Evaluate redemption limits

  # Scope
  * global - Limit is applied across all users.
  * user - Limit is applied per user.

  # Type
  * count - Limit is based on the number of redemptions.
  * rate_limit - Limit is based on the rate of redemptions.
  
  # Opts
  * member_id :: String.t() (Default nil) - If provided, user based limits will be evaluated.
  * as_of :: DateTime.t() (Default now()) - The reference point in time to evaluate rate limits.
  """
  @spec evaluate!(Redemption.t(), Keyword.t()) :: limit_result
  def evaluate!(%Redemption{id: id, limits: limits}, opts \\ []) do
    query = base_query(id)
    for limit &lt;- limits do
      evaluate_limit(limit, query, opts)
    end
  end  

  # Global rate limit
  def evaluate_limit(%{id: id, limit_scope: :global, limit_type: :rate_limit, limit: limit} = l, query, opts) do
    as_of = Map.get(opts, :as_of, DateTime.utc_now())
    interval = l.interval
    unit = Atom.to_string(l.unit)
 
    query = from(
      base_query,
      [redemption: r, redemption_history: rh],
      select: %{
        id: ^id,
        limit_scope: :global,
        limit_type: :rate_limit,
        interval: type(^interval, :integer),
        unit: type(^unit, :string),
        limit: type(^limit, :integer),
        blocking:
          filter(
            count(rh.id),
            rh.member_id == ^member_id and rh.inserted_at &amp;gt;= datetime_add(^as_of, -1 * ^interval, ^unit)
          ) &amp;gt;= type(^limit, :integer),
        count:
          filter(
            count(rh.id),
            rh.member_id == ^member_id and rh.inserted_at &amp;gt;= datetime_add(^as_of, -1 * ^interval, ^unit)
          )
      }
    )
    Repo.one(query)
  end
    
  # ... Functions for all other limit types 
    
  def evaluate_limit(_, _, _), do: %{}
end    
        &lt;/code&gt;&lt;/pre&gt;
        &lt;div&gt;This approach is an improvement, but still requires multiple queries.&lt;/div&gt;
        &lt;div&gt;&lt;br /&gt;&lt;/div&gt;
        &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;One Query using Select Merge:&lt;/b&gt;&amp;nbsp; A solid solution IMO, which requires only one query, is to use &lt;a href="https://hexdocs.pm/ecto/Ecto.Query.html#select_merge/3" target="_blank"&gt;Ecto's select_merge&lt;/a&gt;.&amp;nbsp; We can use the list of RedemptionLimits to generate one query which will evaluate all the limits at the DB level.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;

              &lt;pre&gt;&lt;code&gt;
defmodule RedemptionLimits do

  @doc """
  Evaluate redemption limits

  # Scope
  * global - Limit is applied across all users.
  * user - Limit is applied per user.

  # Type
  * count - Limit is based on the number of redemptions.
  * rate_limit - Limit is based on the rate of redemptions.
  
  # Opts
  * member_id :: String.t() (Default nil) - If provided, user based limits will be evaluated.
  * as_of :: DateTime.t() (Default now()) - The reference point in time to evaluate rate limits.
  """
  @spec evaluate!(Redemption.t(), Keyword.t()) :: limit_result
  def evaluate!(%Redemption{id: id, limits: limits}, opts \\ []) do
    query = base_query(id)
    opts = Map.new(opts)
    query = Enum.reduce(limits, query, &amp;amp;gen_select(&amp;amp;1, &amp;amp;2, opts))
    Repo.one(query)
  end

  @doc """
  Merge a select into an existing query based on the limit configuration.

  ## Opts
  * member_id :: String.t() (Default nil) - If provided, user based limits will be evaluated.
  * as_of :: DateTime.t() (Default now()) - The reference point in time to evaluate rate limits.
  """
  @spec gen_select(limit :: RedemptionLimit.t(), query :: Ecto.Query.t(), opts :: map()) :: Ecto.Query.t()
  def gen_select(limit, query, opts \\ %{})

  # Global count limit
  def gen_select(%{id: id, limit_scope: :global, limit_type: :count, limit: limit}, query, _member) do
    select_merge(query, [redemption: r, redemption_history: rh], %{
      ^id =&amp;gt; %{
        id: ^id,
        limit_scope: :global,
        limit_type: :count,
        limit: type(^limit, :integer),
        blocking: count(rh.id) &amp;gt;= type(^limit, :integer),
        count: count(rh.id)
      }
    })
  end

  # User count limit
  def gen_select(%{id: id, limit_scope: :user, limit_type: :count, limit: limit}, query, %{member_id: member_id})
      when is_binary(pid) do
    select_merge(query, [redemption: r, redemption_history: rh], %{
      ^id =&amp;gt; %{
        id: ^id,
        limit_scope: :user,
        limit_type: :count,
        limit: type(^limit, :integer),
        blocking: filter(count(rh.id), rh.member_id == ^member_id) &amp;gt;= type(^limit, :integer),
        count: filter(count(rh.id), rh.member_id == ^member_id)
      }
    })
  end

  # Global rate limit
  def gen_select(%{id: id, limit_scope: :global, limit_type: :rate_limit, limit: limit} = l, query, opts) do
    as_of = Map.get(opts, :as_of, DateTime.utc_now())
    interval = l.interval
    unit = Atom.to_string(l.unit)

    select_merge(query, [redemption: r, redemption_history: rh], %{
      ^id =&amp;gt; %{
        id: ^id,
        limit_scope: :global,
        limit_type: :rate_limit,
        interval: type(^interval, :integer),
        unit: type(^unit, :string),
        limit: type(^limit, :integer),
        blocking:
          filter(
            count(rh.id),
            rh.inserted_at &amp;gt;= datetime_add(^as_of, -1 * ^interval, ^unit)
          ) &amp;gt;= type(^limit, :integer),
        count:
          filter(
            count(rh.id),
            rh.inserted_at &amp;gt;= datetime_add(^as_of, -1 * ^interval, ^unit)
          )
      }
    })
  end
  
  # ... Functions for all other limit types
  
  def gen_select(_limit, query, _opts), do: query
end
              &lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
  &lt;h2 style="text-align: left;"&gt;How does this work?&lt;/h2&gt;&lt;div&gt;When you use select_merge, the result must be a map.&amp;nbsp; Ecto generates a query that executes all select statements, and then merges the results into a map as a result.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;In the example above, each limit generates a new select statement which returns a map. The map key is the redemption limit ID.&amp;nbsp; This makes it very easy to pick an evaluated limit from the result map.&amp;nbsp; See the example test below.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div style="background-color: #2b2b2b; color: #a9b7c6;"&gt;&lt;pre&gt;&lt;br /&gt;  assert %&lt;span style="color: #48b9e6;"&gt;{&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #48b9e6;"&gt;         &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;:redemption_id &lt;/span&gt;&lt;span style="color: #97e649;"&gt;=&amp;gt; ^&lt;/span&gt;rid&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;         &lt;/span&gt;&lt;span style="color: #97e649;"&gt;^&lt;/span&gt;global_limit_id &lt;span style="color: #97e649;"&gt;=&amp;gt; &lt;/span&gt;%&lt;span style="color: #48b9e6;"&gt;{&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #48b9e6;"&gt;           &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;blocking&lt;/span&gt;: &lt;span style="color: #cc7832;"&gt;false,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;           &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;count&lt;/span&gt;: &lt;span style="color: #6897bb;"&gt;5&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;           &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;limit&lt;/span&gt;: &lt;span style="color: #6897bb;"&gt;6&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;           &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;limit_scope&lt;/span&gt;: &lt;span style="color: #9876aa;"&gt;:global&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;           &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;limit_type&lt;/span&gt;: &lt;span style="color: #9876aa;"&gt;:count&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #9876aa;"&gt;         &lt;/span&gt;&lt;span style="color: #48b9e6;"&gt;}&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;         &lt;/span&gt;&lt;span style="color: #97e649;"&gt;^&lt;/span&gt;user_limit_id &lt;span style="color: #97e649;"&gt;=&amp;gt; &lt;/span&gt;%&lt;span style="color: #48b9e6;"&gt;{&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #48b9e6;"&gt;           &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;blocking&lt;/span&gt;: &lt;span style="color: #cc7832;"&gt;true,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;           &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;count&lt;/span&gt;: &lt;span style="color: #6897bb;"&gt;3&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;           &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;limit&lt;/span&gt;: &lt;span style="color: #6897bb;"&gt;3&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;           &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;limit_scope&lt;/span&gt;: &lt;span style="color: #9876aa;"&gt;:user&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;,&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #cc7832;"&gt;           &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;limit_type&lt;/span&gt;: &lt;span style="color: #9876aa;"&gt;:count&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #9876aa;"&gt;         &lt;/span&gt;&lt;span style="color: #48b9e6;"&gt;}&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #48b9e6;"&gt;       } &lt;/span&gt;&lt;span style="color: #97e649;"&gt;= &lt;/span&gt;RedemptionLimits&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;evaluate!&lt;span style="color: #e6e649;"&gt;(&lt;/span&gt;redemption&lt;span style="color: #cc7832;"&gt;, &lt;/span&gt;&lt;span style="color: #9876aa;"&gt;member_id&lt;/span&gt;: member&lt;span style="color: #49e697;"&gt;.&lt;/span&gt;id&lt;span style="color: #e6e649;"&gt;)&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;pre&gt;&lt;span style="color: #e6e649;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

  &lt;br /&gt;
  &lt;div&gt;Using this approach with aggregates is beneficial as Postgres will more efficiently retrieve and aggregate the data when it is all one query, rather than multiple queries.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Adding a new limit is very easy, we simply add a new gen_select function for the new limit type.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Evaluating a subset of limits is also very straight forward, we just filter the list before generating the query.&amp;nbsp;&amp;nbsp;&lt;/div&gt;&lt;h2 style="text-align: left;"&gt;Conclusion&lt;/h2&gt;&lt;div&gt;I hope you find this post useful.&amp;nbsp; There are better introductions to select_merge, but this is a real life use case that I thought others would find interesting and useful.&lt;/div&gt;
&lt;/div&gt;</description><author>Milwaukee Maven</author><pubDate>Tue, 26 Nov 2024 03:09:29 GMT</pubDate><guid isPermaLink="true">https://blog.andyglassman.com/2024/11/ecto-using-selectmerge-for-dynamic.html</guid></item><item><title>How I grew my tech blog to 35,000 readers in a year</title><link>https://zackproser.com/blog/35-thousand-readers-update</link><description>Learn how I grew my tech blog to 35,000 readers in one year through site optimization, UX improvements, consistent publishing, and building useful tools.</description><author>Zachary Proser</author><pubDate>Tue, 26 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/35-thousand-readers-update</guid></item><item><title>The Next 31 Years of Developing Unum</title><link>https://ashvardanian.com/posts/next-31-years-of-unum/</link><description>Nine years into a 40-year commitment to AI infrastructure. Reflecting on mistakes, open-source wins, and why the real work is just beginning.</description><author>Ash's Blog</author><pubDate>Tue, 26 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/next-31-years-of-unum/</guid></item><item><title>How to Flatpack Programs</title><link>https://youtu.be/rJcQ45jKuN4?si=RbeUh3cqLgGUV_UT</link><description>To frugally furnish a codebase, imitate economical furniture design.</description><author>taylor.town</author><pubDate>Tue, 26 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://youtu.be/rJcQ45jKuN4?si=RbeUh3cqLgGUV_UT</guid></item><item><title>Leaving Proton Mail</title><link>https://nelson.cloud/leaving-proton-mail/?ref=rss</link><description>Reasons why I won&amp;rsquo;t renew my Proton subscription.</description><author>Nelson Figueroa</author><pubDate>Tue, 26 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/leaving-proton-mail/?ref=rss</guid></item><item><title>Crafting the perfect intro</title><link>https://ilearnt.com/blog/theperfectintro/</link><description>&lt;p&gt;In an &lt;a href="https://nathanbarry.com/the-best-way-to-answer-so-what-do-you-do-2025-clay-hebert-054/" target="_blank"&gt;interview&lt;/a&gt;, Clay Herbert walks Nathan Barry through creating the best answer to the question &amp;ldquo;So what do you do?&amp;rdquo;. We have all been asked that at some point when meeting someone new.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Mon, 25 Nov 2024 22:50:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/theperfectintro/</guid></item><item><title>IdentityServer in Docker Containers: Networking (Part 2)</title><link>https://nestenius.se/net/identityserver-in-docker-containers-part-2/</link><description>&lt;p&gt;This is part 2 of a blog series on containerizing a Duende IdentityServer and a client application. In this post, we resolve communication challenges that arise when these applications run in separate Docker containers. You&amp;#8217;ll learn how to fix back-channel issues, handle localhost conflicts, and establish proper networking between the client and IdentityServer. This blog [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/net/identityserver-in-docker-containers-part-2/"&gt;IdentityServer in Docker Containers: Networking (Part 2)&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Mon, 25 Nov 2024 21:07:51 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/net/identityserver-in-docker-containers-part-2/</guid></item><item><title>Moving back to Japan - countryside</title><link>https://dostoynikov.bearblog.dev/moving-back-to-japan-countryside/</link><description>&lt;p&gt;After a two-month writing hiatus, I'm back to share my recent adventure: moving back to Japan.&lt;/p&gt;
&lt;p&gt;Two weeks ago, I left my croupier job in Estonia, moved back to Japan and already achieved one of my long-standing dreams – settling into a charming two-story house with a garden. My plan is to transform this outdoor space into a green haven, with lemon trees and herb-filled pots(Yes I love lemon trees a lot and fortunately there are lemon trees everywhere around my neighborhood). The house was completely empty when I moved in, so I'm gradually acquiring basic appliances to make it feel like home.&lt;/p&gt;
&lt;p&gt;Transportation here presented an interesting challenge. While a car is essential in the countryside, my current financial situation – thanks to the loans I took for this journey – means I've opted for a bicycle. I purchased a Giant Escape for around 70,000 yen and will be commuting 5km each way, a distance familiar from my university days.&lt;/p&gt;
&lt;p&gt;This isn't my first time in Japan, but living in the countryside as a foreigner feels like a unique privilege. The locals are incredibly welcoming. While I'm visibly different – a bearded foreigner in a small Japanese city – I've been pleasantly surprised by the warm interactions. Elders, children, and dog owners are particularly friendly, often greeting me with enthusiastic "konnichiwa"s.&lt;/p&gt;
&lt;p&gt;My ability to speak Japanese fluently is making a big difference, of course. In this small city, encountering a Turk who speaks Japanese at an advanced level is not something locals experience everyday. Whenever I need assistance, people go out of their way to help me, which I deeply appreciate.&lt;/p&gt;
&lt;p&gt;My professional journey is just beginning. Starting December 2nd, I'll join a new workplace where I've already met most of my colleagues. Contrary to stereotypes about Japanese companies, my new team is quite supportive and genuinely interested in making me feel comfortable. I'm aware that I'm really lucky to have found such an environment, and I'm hopeful about building a long-term community here.&lt;/p&gt;</description><author>ᓚᘏᗢdostoynikov</author><pubDate>Mon, 25 Nov 2024 17:46:14 GMT</pubDate><guid isPermaLink="true">https://dostoynikov.bearblog.dev/moving-back-to-japan-countryside/</guid></item><item><title>Useful things I’ve 3D printed</title><link>https://nathanfriend.com/2024/11/25/useful-things-ive-3d-printed.html</link><description>3D printers are ridiculously cool. There’s something about seeing a digital object slowly manifest in the real world that is pure magic.</description><author>Nathan Friend</author><pubDate>Mon, 25 Nov 2024 15:48:41 GMT</pubDate><guid isPermaLink="true">https://nathanfriend.com/2024/11/25/useful-things-ive-3d-printed.html</guid></item><item><title>The Umbrella Academy (Season 4): Not the best but for the fans</title><link>https://olshansky.info/tv/the_umbrella_academy_season_4/</link><description>Olshansky's review of The Umbrella Academy: Season 4</description><author>🦉 olshansky 🦁</author><pubDate>Mon, 25 Nov 2024 11:47:17 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/the_umbrella_academy_season_4/</guid></item><item><title>Success</title><link>https://martinrue.com/success</link><description>5 traits that disproportionately contribute to success.</description><author>Martin Rue</author><pubDate>Mon, 25 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/success</guid></item><item><title>How I syndicate links and notes to Bluesky with GitHub Actions</title><link>https://qubyte.codes/blog/how-i-syndicate-links-and-notes-to-bluesky-with-github-actions</link><description>&lt;p&gt;Inspired by &lt;a href="https://adactio.com/journal/21570"&gt;Jeremy's post on how he syndicates to Bluesky&lt;/a&gt;, I
thought I'd follow suit (many examples are useful when it comes to API
integration work). A disclaimer though... I'm dubious of the long term prospects
of Bluesky for reasons I won't go into here. That being said, it's currently
a vibrant place, and &lt;a href="https://indieweb.org/POSSE"&gt;syndicating from my site to other places&lt;/a&gt; keeps my
content&lt;sup class="footnote-ref"&gt;&lt;a href="#footnote-1" id="footnote-ref-1"&gt;[1]&lt;/a&gt;&lt;/sup&gt; in my hands.&lt;/p&gt;
&lt;p&gt;My setup is a bit of a Rube Goldberg machine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I publish notes (optionally with a photo) and bookmarks using &lt;a href="https://www.w3.org/TR/micropub/"&gt;Micropub&lt;/a&gt;
endpoints.&lt;/li&gt;
&lt;li&gt;The endpoints add the note or bookmark as a &lt;a href="https://jf2.spec.indieweb.org"&gt;JF2&lt;/a&gt; JSON file to the &lt;a href="https://github.com/qubyte/qubyte-codes"&gt;git
repository of my personal site on GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://github.com/qubyte/qubyte-codes/blob/main/.github/workflows/syndicate-to-bluesky.yml"&gt;GitHub Actions workflow&lt;/a&gt; is triggered by the addition of a note
or bookmark.&lt;/li&gt;
&lt;li&gt;The workflow calls a Node.js script. It calls the script using my Bluesky
handle, which is &lt;code&gt;qubyte.codes&lt;/code&gt; for me, and an &lt;a href="https://bsky.app/settings/app-passwords"&gt;app password&lt;/a&gt;. Do &lt;em&gt;not&lt;/em&gt; use
your actual password! Bluesky makes it pretty easy to create an
&lt;a href="https://bsky.app/settings/app-passwords"&gt;app password&lt;/a&gt;, and when you have one you can add it as a secret for Actions
to use at the &lt;code&gt;./settings/secrets/actions&lt;/code&gt; path of your repo site. I've called
my secret &lt;code&gt;BLUESKY_APP_PASSWORD&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The rest of this post is about &lt;a href="https://github.com/qubyte/qubyte-codes/blob/main/scripts/syndicate-to-bluesky.js"&gt;the script itself&lt;/a&gt;. As a rule of thumb,
I keep bits of shell glue in Actions workflows, and store the bulk of any logic
in a discrete script. That makes it easy to call the script by hand for testing.&lt;/p&gt;
&lt;p&gt;I publish notes (optionally with an image) and bookmarks using &lt;a href="https://www.w3.org/TR/micropub/"&gt;Micropub&lt;/a&gt;
endpoints. The note or bookmark is added as a &lt;a href="https://jf2.spec.indieweb.org"&gt;JF2&lt;/a&gt; JSON file to the &lt;a href="https://github.com/qubyte/qubyte-codes"&gt;git
repository of my personal site on GitHub&lt;/a&gt;. When such a file is created
there, a &lt;a href="https://github.com/qubyte/qubyte-codes/blob/main/.github/workflows/syndicate-to-bluesky.yml"&gt;GitHub Actions workflow&lt;/a&gt; is triggered, and this workflow
in turn calls a Node.js script.&lt;/p&gt;
&lt;p&gt;The script is a vanilla Node.js script. While Bluesky does provide API client
libraries, I didn't find it necessary to use one. I managed to write this
without any third party libraries. API work is all achieved with plain old
&lt;code&gt;fetch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are a couple of local libraries which I've extracted into their own
modules. The first is &lt;code&gt;blueskyAuth&lt;/code&gt;, which looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-javascript"&gt;&lt;span class="hljs-keyword"&gt;const&lt;/span&gt; createSessionUrl = &lt;span class="hljs-string"&gt;&amp;#x27;https://bsky.social/xrpc/com.atproto.server.createSession&amp;#x27;&lt;/span&gt;;

&lt;span class="hljs-keyword"&gt;export&lt;/span&gt; &lt;span class="hljs-keyword"&gt;default&lt;/span&gt; &lt;span class="hljs-keyword"&gt;async&lt;/span&gt; &lt;span class="hljs-keyword"&gt;function&lt;/span&gt; &lt;span class="hljs-title function_"&gt;blueskyAuth&lt;/span&gt;(&lt;span class="hljs-params"&gt;handle, appPassword&lt;/span&gt;) {
  &lt;span class="hljs-keyword"&gt;const&lt;/span&gt; res = &lt;span class="hljs-keyword"&gt;await&lt;/span&gt; &lt;span class="hljs-title function_"&gt;fetch&lt;/span&gt;(createSessionUrl, {
    &lt;span class="hljs-attr"&gt;headers&lt;/span&gt;: { &lt;span class="hljs-string"&gt;&amp;#x27;content-type&amp;#x27;&lt;/span&gt;: &lt;span class="hljs-string"&gt;&amp;#x27;application/json&amp;#x27;&lt;/span&gt; },
    &lt;span class="hljs-attr"&gt;method&lt;/span&gt;: &lt;span class="hljs-string"&gt;&amp;#x27;POST&amp;#x27;&lt;/span&gt;,
    &lt;span class="hljs-attr"&gt;body&lt;/span&gt;: &lt;span class="hljs-title class_"&gt;JSON&lt;/span&gt;.&lt;span class="hljs-title function_"&gt;stringify&lt;/span&gt;({ &lt;span class="hljs-attr"&gt;identifier&lt;/span&gt;: handle, &lt;span class="hljs-attr"&gt;password&lt;/span&gt;: appPassword })
  });

  &lt;span class="hljs-keyword"&gt;if&lt;/span&gt; (!res.&lt;span class="hljs-property"&gt;ok&lt;/span&gt;) {
    &lt;span class="hljs-keyword"&gt;throw&lt;/span&gt; &lt;span class="hljs-keyword"&gt;new&lt;/span&gt; &lt;span class="hljs-title class_"&gt;Error&lt;/span&gt;(
      &lt;span class="hljs-string"&gt;`Bluesky responded with an unexpected status: &lt;span class="hljs-subst"&gt;${res.status}&lt;/span&gt; &lt;span class="hljs-subst"&gt;${&lt;span class="hljs-keyword"&gt;await&lt;/span&gt; res.text()}&lt;/span&gt;`&lt;/span&gt;
    );
  }

  &lt;span class="hljs-keyword"&gt;return&lt;/span&gt; res.&lt;span class="hljs-title function_"&gt;json&lt;/span&gt;(); &lt;span class="hljs-comment"&gt;// { accessJwt, refreshJwt, did }&lt;/span&gt;
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Bluesky mostly speaks JSON, which is convenient when working in Node.js. To
create a session with Bluesky, you JSON encode an object containing your handle
and an app password. If successful, the response contains three things of
interest:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;accessJwt&lt;/code&gt;: A short-lived token used for API requests.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;refreshJwt&lt;/code&gt;: A token which can be exchanged for a new &lt;code&gt;accessJwt&lt;/code&gt; and
&lt;code&gt;refreshJwt&lt;/code&gt; when the &lt;code&gt;accessJwt&lt;/code&gt; expires.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;did&lt;/code&gt;: An identifier. The initial &amp;quot;d&amp;quot;&lt;sup class="footnote-ref"&gt;&lt;a href="#footnote-2" id="footnote-ref-2"&gt;[2]&lt;/a&gt;&lt;/sup&gt; stands for &amp;quot;distributed&amp;quot;, but I recently discovered that the DID &lt;code&gt;plc&lt;/code&gt;
method used by Bluesky is not actually distributed at all, thanks to Christine
Lemmer-Webber's &lt;a href="https://dustycloud.org/blog/how-decentralized-is-bluesky/"&gt;excellent article on Bluesky and decentralization&lt;/a&gt;. I
&lt;em&gt;highly&lt;/em&gt; recommend reading it to dispel some myths around how decentralized
Bluesky is, by design.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;My script uses the &lt;code&gt;accessJwt&lt;/code&gt; as a token and the &lt;code&gt;did&lt;/code&gt; as an identifier in
API requests. Since the session is only needed to post one document (and
possibly an image), there's no need to think about refreshing the token, so I
don't use the &lt;code&gt;refreshJwt&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The rest of the script is about one or two API requests. When an image is to be
uploaded, that happens first. There's not much to say here beyond the size
restrictions being quite low, and being constrained to JPEGs and PNGs. I don't
think they're doing much processing of images, so I recommend removing metadata
from image uploads. The response of the image upload contains a &lt;code&gt;blob&lt;/code&gt; field,
which is used as a reference to the image in the second API request in the form
of an &lt;em&gt;&lt;a href="https://docs.bsky.app/docs/advanced-guides/posts#images-embeds"&gt;embedding&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The final request creates the &amp;quot;record&amp;quot; (the note or bookmark) on Bluesky.
There's a lot to unpack here. The &lt;a href="https://docs.bsky.app/docs/tutorials/creating-a-post"&gt;&lt;em&gt;creating a post&lt;/em&gt;&lt;/a&gt; is full of
useful samples to help figure out the anatomy of a record creation body.&lt;/p&gt;
&lt;p&gt;One fascinating aspect of Bluesky is how text is composed. Rather than some sort
of markup language, it uses a more limited concept called &lt;em&gt;&lt;a href="https://docs.bsky.app/docs/advanced-guides/post-richtext"&gt;rich text facets&lt;/a&gt;&lt;/em&gt;.
Text is linear, and facets (for example, links) are attached to UTF-8 byte
ranges of the text. This is awkward for many languages! For example, JavaScript
uses UTF-16 to represent string internally (as was the trend in the mid-'90s),
so the range of characters you get with naïve string work in JS will give you
incorrect offsets. Thankfully Node.js has the venerable &lt;code&gt;Buffer&lt;/code&gt; class, which
can be used to represent strings as arrays of UTF-8 bytes. A &lt;code&gt;Buffer.byteLength&lt;/code&gt;
is all it takes to get the UTF-8 size of a string.&lt;/p&gt;
&lt;p&gt;Anyway, I've put lots of annotations in &lt;a href="https://github.com/qubyte/qubyte-codes/blob/main/scripts/syndicate-to-bluesky.js"&gt;the syndication script&lt;/a&gt;, so
hopefully it serves as a useful example!&lt;/p&gt;</description><author>Qubyte Codes</author><pubDate>Mon, 25 Nov 2024 04:15:00 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/blog/how-i-syndicate-links-and-notes-to-bluesky-with-github-actions</guid></item><item><title>LLM iterate and insert</title><link>https://xenodium.com/llm-iterate-and-insert</link><description>&lt;p&gt;&lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt; includes a couple of mechanisms to operate on an Emacs buffer region. That is, select a region and ask the &lt;a href="https://lmno.lol/alvaro/chatgpt-shell-goes-multi-model"&gt;LLM robots&lt;/a&gt; to modify it for us. Until now, both of these mechanisms didn't quite close the loop. They could either modify current region or iterate on a separate solution, but never both.&lt;/p&gt;
&lt;h2&gt;M-x chatgpt-shell-quick-insert&lt;/h2&gt;
&lt;p&gt;While &lt;code&gt;chatgpt-shell&lt;/code&gt;'s quick insert mechanism already enabled selecting a region and requesting changes, it was more of a &amp;quot;I'm feeling lucky&amp;quot; situation. If the changes didn't pan out, you'd have to discard the suggestion and start over.&lt;/p&gt;
&lt;p&gt;With the latest changes, in addition to accepting or discarding (y/n bindings) suggestions, we can now iterate using the &lt;code&gt;i&lt;/code&gt; binding.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="http://localhost:8787/alvaro/llm-iterate-and-insert/quick-insert-iterate.gif" title="1" /&gt;&lt;/p&gt;
&lt;h2&gt;M-x chatgpt-shell-prompt-compose&lt;/h2&gt;
&lt;p&gt;While quick insertions rely on minibuffer input, &lt;code&gt;chatgpt-shell-prompt-compose&lt;/code&gt; opens a dedicated buffer for a more thorough interactions. Select a region, invoke &lt;code&gt;chatgpt-shell-prompt-compose&lt;/code&gt; (my preferred binding being &lt;code&gt;C-c C-e&lt;/code&gt;), and a new compose buffer is created with the region text. Add your instructions and submit with &lt;code&gt;C-c C-c&lt;/code&gt;. Post submission, the compose buffer becomes read-only and single-character key-bindings take over. For example: &lt;code&gt;r&lt;/code&gt; replies (for further iteration) and &lt;code&gt;n/p&lt;/code&gt; (or &lt;code&gt;TAB/shift-TAB&lt;/code&gt;) navigation. There are &lt;a href="https://github.com/xenodium/chatgpt-shell?tab=readme-ov-file#a-shell-hybrid"&gt;more bindings&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Until now, we could easily craft more involved queries and continue iterating from the compose buffer, but integrating suggested changes was a manual process. That is, navigate to a code block, select, copy it and then paste it elsewhere.&lt;/p&gt;
&lt;p&gt;With the latest changes, pressing &lt;code&gt;i&lt;/code&gt; (insert) while on a code block will attempt to insert it wherever the initial interaction took place.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="http://localhost:8787/alvaro/llm-iterate-and-insert/compose-insert-iterate.gif" title="1" /&gt;&lt;/p&gt;
&lt;p&gt;Both of these changes show now be available on MELPA. While I demoed ChatGPT, the two mechanism should now work with any of the supported models (ChatGPT, Claude, Gemini, and Ollama).&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Mon, 25 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/llm-iterate-and-insert</guid></item><item><title>Understanding SIMD: Infinite Complexity of Trivial Problems 🔥</title><link>https://ashvardanian.com/posts/understanding-simd-complexity/</link><description>Why SIMD programming is harder than it looks: exploring cosine similarity optimization across x86 and Arm architectures, from basic AVX2 to cutting-edge SVE2 instructions.</description><author>Ash's Blog</author><pubDate>Mon, 25 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/understanding-simd-complexity/</guid></item><item><title>Mostly Harmless</title><link>https://vit.baisa.cz/books/mostly-harmless/</link><description/><author>Vít Baisa</author><pubDate>Mon, 25 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/books/mostly-harmless/</guid></item><item><title>Allgood - Instant healthcheck webpage and API for JS/TS projects</title><link>/allgood/</link><description>&lt;p&gt;Over the weekend, I shipped a new open source project - allgood. It's an npm module designed to instantly add a &lt;code&gt;/healthcheck&lt;/code&gt; page to your app. Out the box it supports Express, Fastify and Hono. It could be adapted to use Next as well (although I haven't tested this).&lt;/p&gt;
&lt;p&gt;After you set it up, you get a page like this:
&lt;img src="../../assets/images/allgood/allgood.png" /&gt;&lt;/p&gt;
&lt;p&gt;You can find the &lt;a href="https://github.com/joshghent/allgood"&gt;code on GitHub&lt;/a&gt; and the &lt;a href="https://www.npmjs.com/package/@joshghent/allgood"&gt;library published on NPM&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It was a fun project to build and satisfying to ship something in a weekend. I haven't built an npm module in over 5 years(!) so it was nice from that perspective also.&lt;/p&gt;
&lt;h2&gt;Why I built allgood&lt;/h2&gt;
&lt;p&gt;It mainly boiled down to 3 key reasons:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Shipping is good. It's often easy to get caught up in the trap of endless delivery timelines, interspaced with laborious planning meetings, discussions and the like. Creating something and putting it out into the world is amazing. And this project was something small I could deliver and provide value. It was satisfying to actually see the project live.&lt;/li&gt;
&lt;li&gt;Open source is good. A lot of my recent programming work outside of the jobby job has been on commercially focused products (a side hustle if you will). To avoid the complexity that billing and marketing bring, I wanted to create something entirely free and for no tangible benefit to myself other than self-satisfaction. Doing so enabled me to focus on the code and to craft a nice API (internal) - thinking about extensibility and simplicity.&lt;/li&gt;
&lt;li&gt;It's a tool I'll use. Like most, I scratch my own itches. It's one of the great things about knowing how to code. You stumble across a problem you have, and you can solve it!&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;How I built it&lt;/h2&gt;
&lt;p&gt;By far, the most difficult part was setting up the build system so that it worked with both &lt;code&gt;require&lt;/code&gt; and &lt;code&gt;import&lt;/code&gt; syntax. It was a total pain, and tells me something not that nice about the state of the JS ecosystem - I digress. I knew this was going to be a pain though so I figured I'd use a boilerplate. The one I landed on needed a little bit of fudging but I got there in the end.&lt;/p&gt;
&lt;p&gt;Afterwards, I got started on writing the check code. I started with memory usage as I thought this would be the easiest - simply using &lt;code&gt;process.memoryUsage()&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now though, there was a challenge. The user could configure N checks to run on their healthcheck page. I needed to run all of the checks (in parallel, for speed) and then display the results in a consistent order. I also needed to call the correct check function.
The solution I landed on was for all checks to use a consistent interface. This meant I could guarantee the output of each check was consistent. They are sort of decoupled from the app itself meaning the display message and other properties are entirely within their control.&lt;/p&gt;
&lt;p&gt;Here is how the interface looked&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-ts"&gt;export interface HealthCheck {
  status: Status;
  value: string;
  componentName: string;
  message: string;
  time: number; // Time in milliseconds that elapsed running the check
}

// The interface that each check implements
export interface CheckFn {
  (config: Config): Promise&amp;lt;HealthCheck&amp;gt;
}

export interface CheckRegistry {
  [key: string]: CheckFn;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I then created an object mapping of &amp;quot;checks&amp;quot; to their functions. And then tethered it together with &lt;code&gt;.maps&lt;/code&gt; and &lt;code&gt;Promise.all&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Knowing my own monkey mind, I wanted to make this as maintenance free as possible. So when new dependency PR's are created, I can just merge them straight away. Therefore, I took some time to create a test suite. Initially I wanted to use the new native node test suite. But after trying to wrangle with mocks I gave up and used Jest instead - which just worked.&lt;/p&gt;
&lt;p&gt;One surprising outcome of this project was the package manager. Using pnpm was a really nice interface and extremely quick. There wasn't a lot of dependencies to install but I never got into the usual hassle of dependency wrangling I usually do.&lt;/p&gt;
&lt;h2&gt;The future&lt;/h2&gt;
&lt;p&gt;I'm not sure what this project will yield but after sending it round to some colleagues I've got a good response and had some good feature requests. It's fun to code and create things purely for the sake of the act of creation. If you know how to code, do the same - you'll feel amazing.&lt;/p&gt;</description><author/><pubDate>Mon, 25 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">/allgood/</guid></item><item><title>Terminology isn't universal</title><link>https://ntietz.com/blog/terminology-isnt-universal/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;A little while back, I wrote that we shouldn't say "auth" but should use &lt;a href="https://ntietz.com/blog/lets-say-instead-of-auth/"&gt;other terms instead&lt;/a&gt;.
I stand by my argument in general, but it also has another side to it: my suggested terminology makes sense in some domains, but not in &lt;em&gt;all&lt;/em&gt; domains.
And that's because terminology isn't—and &lt;em&gt;can't be&lt;/em&gt;—universal.&lt;/p&gt;
&lt;p&gt;After I wrote that blog post, I got an email from someone who had a really good point: the word "login" is often confused for "username"!
My terminology suggestion doesn't work in all cases, so what's the right word to use instead?
Unfortunately... there isn't one.
There's no one pair of words that will work perfectly.&lt;/p&gt;
&lt;p&gt;Terminology makes sense relative to the context it's in.
If I say "node" to you, what does that mean?&lt;/p&gt;
&lt;p&gt;My guess is you will think of either a node in a network/graph or a node as in a server.
But you could also think of a lymph node or myriad other things!&lt;/p&gt;
&lt;p&gt;The other term you could use in a graph context is "vertex".
This is often clearer, but it runs into issues if you're talking about anything with geometry!&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;I ran into this headlong at my first job, working on a &lt;a href="https://en.wikipedia.org/wiki/TigerGraph"&gt;graph database&lt;/a&gt;.
When talking about a graph in a distributed system, if you use the word "node" it is confusing:
Do you mean a node of the graph or a node of the distributed system&lt;sup class="footnote-reference" id="fr-overlapping-1"&gt;&lt;a href="https://ntietz.com/blog/terminology-isnt-universal/#fn-overlapping"&gt;[1]&lt;/a&gt;&lt;/sup&gt;?
We ended up choosing "vertex" to mean the thing in a graph, and that worked—but would not work if we also wanted to represent geometry, like in, say, a graph visualization where you'd be drawing vertices of polygons.&lt;/p&gt;
&lt;p&gt;A lot of this confusion, I think, comes from when different domains &lt;em&gt;overlap&lt;/em&gt; or border each other.
Words which make sense in one domain will leak into the other, but they'll end up being confusing or imprecise there.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;So what do we do about it?&lt;/p&gt;
&lt;p&gt;A lot of it is accepting that we will run into challenges here, and there's not always a perfect solution.
If we run into imprecise or confusing language, that could mean that suboptimal word choices were made.
But there might &lt;em&gt;not&lt;/em&gt; be a better choice!
Some confusion will always be possible, and we have to roll with that and be understanding about it.&lt;/p&gt;
&lt;p&gt;We also have to remember that there still might be a better choice!
Even if it's not perfect, we can go for &lt;em&gt;better&lt;/em&gt;, and we can improve the status quo.
We can choose words that are more clear in &lt;em&gt;our&lt;/em&gt; domain, &lt;em&gt;our&lt;/em&gt; context, and make things clearer there.&lt;/p&gt;
&lt;p&gt;The other thing is we have to accept that terminology can't be universal.
Just because terminology works in one domain doesn't mean it will apply to &lt;em&gt;all&lt;/em&gt; domains.
This is inherent to the whole issue, because if it were universal then there would be one single optimal choice.
But there's not.
And since it's not universal, we sort of have to expect that sometimes we'll run into terms that are imprecise or confusing, and that they're chosen to make sense &lt;em&gt;there&lt;/em&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-overlapping"&gt;
&lt;p&gt;Ultimately some of the confusion in &lt;em&gt;this&lt;/em&gt; case is because the two are related overlapping domains.
Computer networks are represented &lt;em&gt;as&lt;/em&gt; graphs, and we talk about them in those terms to some extent. &lt;a href="https://ntietz.com/blog/terminology-isnt-universal/#fr-overlapping-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 25 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/terminology-isnt-universal/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Insurance Insurance</title><link>https://taylor.town/insurance</link><description>Purchase Premium Insurance-Squared today!</description><author>taylor.town</author><pubDate>Mon, 25 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/insurance</guid></item><item><title>Un serveur VNC vaniteux (Joke over RFB)</title><link>https://ache.one/articles/serveur-vnc-vaniteux</link><description>VNC est un protocole bien connu de partage de bureau distant.
Ce blog post est l'histoire d'un projet d'un WE qui a conduit à fabriquer un VNC « vaniteux ».</description><author>ache: Blog personnel</author><pubDate>Mon, 25 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ache.one/articles/serveur-vnc-vaniteux</guid></item><item><title>Om krisen eller kriget kommer...</title><link>https://liza.io/om-krisen-eller-kriget-kommer.../</link><description>&lt;p&gt;Jag flyttade till en ny stad och fick en perfekt välkomstpresent i min brevlåda:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Krisen" src="krisen.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Om krisen eller kriget kommer&lt;/em&gt; är en broschyr från &lt;a href="https://www.msb.se/"&gt;Myndigheten för samhällsskydd och beredskap (MSB)&lt;/a&gt;. Den skickades ut till alla svenskar nyligen.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Sun, 24 Nov 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/om-krisen-eller-kriget-kommer.../</guid></item><item><title>Configuring VSCode with Nix on macOS</title><link>https://davi.sh/blog/2024/11/nix-vscode/</link><description>&lt;p&gt;Welcome back to the Nix on Mac series! By the end of this post, you’ll be able to fully
configure VSCode through your Nix flake via &lt;code&gt;home-manager&lt;/code&gt;, which we set up in &lt;a href="/blog/2024/02/nix-home-manager/"&gt;part
2&lt;/a&gt;. This includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Custom keybindings and settings&lt;/li&gt;
&lt;li&gt;Installing themes and extensions from a nixpkgs overlay&lt;/li&gt;
&lt;li&gt;Properly aliasing VSCode and other macOS applications to &lt;code&gt;/Applications&lt;/code&gt; for Spotlight&lt;/li&gt;
&lt;/ul&gt;
&lt;!--more--&gt;
&lt;p&gt;Without further ado, let’s dive in!&lt;/p&gt;
&lt;h2 id="installing-vscode"&gt;Installing VSCode&lt;/h2&gt;
&lt;p&gt;Nix won’t install non-free software like VSCode without you opting in. Add this line
to your &lt;code&gt;nix-darwin&lt;/code&gt; &lt;code&gt;configuration&lt;/code&gt; module:&lt;/p&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;configuration&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; { pkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;,&lt;/span&gt;&lt;span style="color: #F97583;"&gt; ... &lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;}: {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    nixpkgs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;config&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;allowUnfree&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; true&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    &lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;}&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, installing VSCode is just one line of code in our &lt;code&gt;home-manager&lt;/code&gt; config:&lt;/p&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;homeconfig&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; { pkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;,&lt;/span&gt;&lt;span style="color: #F97583;"&gt; ... &lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;}: {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    programs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;vscode&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;        enable&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; true&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;}&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I’m sure many of you who are following along are using VSCode to edit your &lt;code&gt;flake.nix&lt;/code&gt;
file. I should note that installing VSCode through &lt;code&gt;home-manager&lt;/code&gt; will likely blow away
your existing settings when you call &lt;code&gt;switch&lt;/code&gt;. Now would be a good time to back up your
existing settings and extension list so you can replicate your setup from within Nix.&lt;/p&gt;
&lt;p&gt;VSCode will be installed in &lt;code&gt;/Users/$USER/Applications/Home Manager Apps/&lt;/code&gt;&lt;sup&gt;&lt;a href="#user-content-fn-1" id="user-content-fnref-1"&gt;1&lt;/a&gt;&lt;/sup&gt; after
running &lt;code&gt;switch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;If you’ve previously installed VSCode, go ahead and dump the copy from &lt;code&gt;/Applications&lt;/code&gt; in
the trash. &lt;a href="https://media1.tenor.com/m/3idC48k28zcAAAAd/roads-where-were-going-we-dont-need-roads.gif"&gt;Where we’re
going&lt;/a&gt;,
we don’t need &lt;del&gt;roads&lt;/del&gt; globally installed programs!&lt;/p&gt;
&lt;h2 id="editor-settings-and-keybindings"&gt;Editor Settings and Keybindings&lt;/h2&gt;
&lt;p&gt;You can add user settings and keybindings pretty easily:&lt;/p&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;programs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;vscode&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    enable&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; true&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    userSettings&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;        # This property will be used to generate settings.json:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;        # https://code.visualstudio.com/docs/getstarted/settings#_settingsjson&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #9ECBFF;"&gt;        "editor.formatOnSave"&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #79B8FF;"&gt; true&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    keybindings&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;        # See https://code.visualstudio.com/docs/getstarted/keybindings#_advanced-customization&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;        {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;            key&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "shift+cmd+j"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;            command&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "workbench.action.focusActiveEditorGroup"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;            when&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "terminalFocus"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    ];&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;}&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It almost looks like JSON! And in fact, converting Nix types to and from JSON is &lt;a href="https://nixos.org/manual/nix/stable/language/builtins.html#builtins-toJSON"&gt;part of
the Nix standard
library&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="extensions"&gt;Extensions&lt;/h2&gt;
&lt;p&gt;A huge part of VSCode is the bountiful extension ecosystem. &lt;code&gt;home-manager&lt;/code&gt; lets you
install extensions similarly to how it installs packages. The full &lt;a href="https://marketplace.visualstudio.com/vscode"&gt;VSCode
marketplace&lt;/a&gt; isn’t present in nixpkgs by
default, so we’ll need to install an overlay.&lt;/p&gt;
&lt;p&gt;Nixpkgs overlays let you override and add new entries to nixpkgs. We can add the
&lt;a href="https://github.com/nix-community/nix-vscode-extensions"&gt;&lt;code&gt;nix-vscode-extensions&lt;/code&gt;&lt;/a&gt; overlay
by adding a line to our &lt;code&gt;nix-darwin&lt;/code&gt; configuration:&lt;/p&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;  inputs&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    nix-vscode-extensions&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;url&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "github:nix-community/nix-vscode-extensions"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;  };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;  outputs&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    inputs@{ self&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #F97583;"&gt;    ,&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; nixpkgs&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #F97583;"&gt;    ,&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; nix-darwin&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #F97583;"&gt;    ,&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; home-manager&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #F97583;"&gt;    ,&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; mac-app-util&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #F97583;"&gt;    ,&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; nix-vscode-extensions&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    }:&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #F97583;"&gt;    let&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;      configuration&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; { pkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;,&lt;/span&gt;&lt;span style="color: #F97583;"&gt; ... &lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;}: {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;        # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;        nixpkgs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;overlays&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;          nix-vscode-extensions&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;overlays&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;default&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;        ];&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;        # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;      };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;      homeconfig&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; { pkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;,&lt;/span&gt;&lt;span style="color: #F97583;"&gt; ...&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;}: {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;        # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;        programs&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;vscode&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;          # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;          userSettings&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;            # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #9ECBFF;"&gt;            "workbench.colorTheme"&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "Dracula Theme"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;          };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;          # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;          extensions&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;            pkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;vscode-marketplace&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;jnoortheen&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;nix-ide&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;            pkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;vscode-marketplace&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;dracula-theme&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;theme-dracula&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;          ];&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;        }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt;      }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;  # ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;vscode-marketplace&lt;/code&gt; is one of the properties that the &lt;code&gt;nix-vscode-extensions&lt;/code&gt; overlay
added to nixpkgs.  Any VSCode extension in the marketplace should be accessible from
&lt;code&gt;pkgs.vscode-marketplace.$AUTHOR.$EXTENSION&lt;/code&gt;, where &lt;code&gt;$AUTHOR.$EXTENSION&lt;/code&gt; is the same as
the &lt;code&gt;itemName&lt;/code&gt; property in the extension’s URL on the &lt;a href="https://marketplace.visualstudio.com/vscode"&gt;extension marketplace
website&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="cleaning-up-lists-of-attributes-with-the-with-clause"&gt;Cleaning up lists of attributes with the &lt;code&gt;with&lt;/code&gt; clause&lt;/h3&gt;
&lt;p&gt;Writing &lt;code&gt;pkgs.vscode-marketplace&lt;/code&gt; in front of every extension will get tedious as as your
list of extenions gets longer and make the list harder to read. Luckily Nix has a language
construct to help with this: in front of any expression, you can type &lt;code&gt;with &amp;#x3c;attribute set&gt;&lt;/code&gt; to bring all attributes within the attribute set into scope. Our &lt;code&gt;extensions&lt;/code&gt; list
can look like:&lt;/p&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;extensions&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt; =&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt; with&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt; pkgs&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;vscode-marketplace&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt;;&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;    jnoortheen&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;nix-ide&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;    dracula-theme&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;theme-dracula&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;]&lt;/span&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Much cleaner!&lt;/p&gt;
&lt;h2 id="playing-nice-with-spotlight"&gt;Playing nice with Spotlight&lt;/h2&gt;
&lt;p&gt;You might have noticed that the version of VSCode that Nix installs doesn’t show up in
Spotlight. Why is that? It’s unfortunately pretty simple: All artifacts that Nix and
&lt;code&gt;home-manager&lt;/code&gt; add to your system are symbolic links, and Spotlight won’t index
symlinks. There’s &lt;a href="https://github.com/nix-community/home-manager/issues/1341"&gt;a very long thread about this on
GitHub&lt;/a&gt; if you’re interested in
reading. From the various options discussed there,
&lt;a href="https://github.com/hraban/mac-app-util"&gt;&lt;code&gt;mac-app-util&lt;/code&gt;&lt;/a&gt; is the easiest way to get
Spotlight working as expected.&lt;/p&gt;
&lt;p&gt;We can add it as another input at the top of our flake and then modify our flake output
to load &lt;code&gt;mac-app-util&lt;/code&gt; in both &lt;code&gt;nix-darwin&lt;/code&gt; and &lt;code&gt;home-manager&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="astro-code github-dark" style="background-color: #24292e; color: #e1e4e8;" tabindex="0"&gt;&lt;code&gt;&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    description&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "My system configuration"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;    inputs&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;        # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;        mac-app-util&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;url&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt; "github:hraban/mac-app-util"&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;    # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt;    in&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;      darwinConfigurations&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #9ECBFF;"&gt;"$HOSTNAME"&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt; nix-darwin&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;lib&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;darwinSystem&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;        modules&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;          # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;          mac-app-util&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;darwinModules&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;default&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;          home-manager&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;darwinModules&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;home-manager&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;          {&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;            # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #B392F0;"&gt;            home-manager&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt;.&lt;/span&gt;&lt;span style="color: #B392F0;"&gt;sharedModules&lt;/span&gt;&lt;span style="color: #F97583;"&gt; =&lt;/span&gt;&lt;span style="color: #E1E4E8;"&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FFAB70;"&gt;                mac-app-util&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;homeManagerModules&lt;/span&gt;&lt;span style="color: #F97583;"&gt;.&lt;/span&gt;&lt;span style="color: #FFAB70;"&gt;default&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;            ];&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #6A737D;"&gt;            # ...&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;          }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;        ];&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;      };&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #E1E4E8;"&gt;    }&lt;/span&gt;&lt;/span&gt;
&lt;span class="line"&gt;&lt;span style="color: #FDAEB7; font-style: italic;"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After running &lt;code&gt;switch&lt;/code&gt;, VSCode should be properly copied to
&lt;code&gt;/Users/$USER/Applications/Home Manager Trampolines&lt;/code&gt; and should be available the next
time Spotlight refreshes.&lt;/p&gt;
&lt;h3 id="stepping-back"&gt;Stepping Back&lt;/h3&gt;
&lt;p&gt;We just included someone’s third-party Nix utility in our flake to do some custom behavior
when Nix installs macOS GUI apps. You probably didn’t notice that &lt;code&gt;mac-app-util&lt;/code&gt; is
written in &lt;strong&gt;Common Lisp&lt;/strong&gt;. I’ve never written Common Lisp, and my guess is you haven’t
either. Even so, with fewer than five lines of code we were able make use of some code that
someone wrote in their favorite niche programming language without needing to figure out
how to build or run that code. Nix handled it all for us! I, for one, think that’s pretty
amazing.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Our Nix config is now able to fully configure and manage VSCode, including installing
extensions and themes. &lt;code&gt;home-manager&lt;/code&gt; has just as deep an integration with many other
utilities. If you haven’t already definitely go through and check out &lt;a href="https://nix-community.github.io/home-manager/options.xhtml"&gt;home-manager’s
config options&lt;/a&gt; to find out
what you can do with your favorite program.&lt;/p&gt;
&lt;p&gt;You can find the full &lt;code&gt;flake.nix&lt;/code&gt; for this installment on GitHub
&lt;a href="https://github.com/davish/nix-on-mac/tree/part-3"&gt;here&lt;/a&gt;. See you in the next one!&lt;/p&gt;
&lt;section class="footnotes"&gt;&lt;h2 class="sr-only" id="footnote-label"&gt;Footnotes&lt;/h2&gt;
&lt;ol&gt;
&lt;li id="user-content-fn-1"&gt;
&lt;p&gt;Notably, this isn’t inside the normal &lt;code&gt;/Applications&lt;/code&gt; folder since &lt;code&gt;home-manager&lt;/code&gt;
can only install programs under your home directory. Even stranger, Spotlight can’t
pick up our alias! We’ll come back and fix this at the end of the post. &lt;a class="data-footnote-backref" href="#user-content-fnref-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>Davis Haupt's Blog</author><pubDate>Sun, 24 Nov 2024 19:00:00 GMT</pubDate><guid isPermaLink="true">https://davi.sh/blog/2024/11/nix-vscode/</guid></item><item><title>Tracking Ethereum Wallet Balance with GetBlock.io</title><link>https://blog.adnansiddiqi.me/tracking-ethereum-wallet-balance-with-getblock-io/</link><description>&lt;p&gt;In this guide, we’re going to demonstrate how to write and run the simplest script for tracking Ethereum wallet balance with GetBlock’s RPC API. What is Token Ownership? Token ownership is just a digital version of holding an asset. It is similar to holding stocks, currencies, or commodities like gold or silver. Each token is linked to an account (or wallet address), and the balance of tokens in that account reflects ownership.  When you acquire tokens, whether you are buying them on an exchange through trading, getting them as staking rewards, or someone giving you as a gift, they are transferred to the respective wallet addresses and you become their rightful owner. This ownership allows you to use these tokens in any possible way. Why is Tracking Tokens Important? Just like you keep an eye on other assets; whether they’re going up or down, tracking digital tokens is also very necessary. Tracking the tokens you own serves many purposes. First, it gives you a clear picture of your portfolio and its standing.  Second, it helps you to stay on top of your investments. Also, it can be useful while tracking “interesting” wallets of whales, developers, team members, and so on. Accessing Ethereum blockchain using GetBlock  API You may be curious about how to retrieve blockchain data, verify wallet balances, and perform various tasks programmatically. Enter GetBlock, a Blockchain-as-a-Service (BaaS) platform that provides a fast and easy API connection to full nodes from 50+ leading blockchain platforms. It takes the headache out of tracking your token holdings.  Think of it as your personal blockchain data assistant that provides quick and reliable access to token information. It facilitates you to request on-chain information of a blockchain without setting up manually with JSON-RPC, REST, and WebSockets. In this post, we are going to access the Ethereum blockchain and we will be fetching the ETH balance of a certain wallet. We will be using the API endpoint that retrieves the balance. I will be showing how you can use APIs in Python. Creating an Access Token To access GetBlock API you should first create an access token: go to GetBlock and sign up. Once you are signed up and verified, you will be welcomed by this dashboard: To access APIs you would need to create an access token first. Initially, no access token is available. Selecting the Blockchain Protocol and Network If you click on the Start Now button, you will be asked to select the protocol, network, and API. I selected Ethereum as the protocol, Mainnet as the network, and JSON-RPC as the API format. Once you click the Get button you will see an entry for Ethereum like the one below: Retrieving the API Endpoint Now click on Ethereum’s entry and you will see something like the below: As you can see, it gives the main API URI along with the access token: https://go.getblock.io/f7f1e4a673301d0fc69a9848848 Please note that this access token is available for this very network only. We have selected the node and the network.  Now all we need is an Ethereum wallet address so that we can find how many ETH coins it has. We are going to use the eth_getBalance method for this purpose.   On the page, you will see a curl example like the one below that you can run on the terminal curl --location --request POST 'https://go.getblock.io/&amp;#60;ACCESS-TOKEN&amp;#62;/' --header 'Content-Type: application/json' --data-raw '{"jsonrpc": "2.0", "method": "eth_getBalance", "params": ["0xfe3b557e8fb62b89f4916b721be55ceb828dbd73", "latest"], "id": "getblock.io"}' You set JSON headers, the method parameter is set to eth_getBalance and the params contain the wallet address. Checking Balance with cURL From the EtherScan website, I just picked a random wallet (0xF977814e90dA44bFA03b6295A0616a897441aceC) which you can visit here. As you can see the balance (at the time of writing the article) is 799,655.363544175804339796 ETH. Now, first, run the cURL request (real access token has been tempered) in the terminal: curl --location --request POST 'https://go.getblock.io/f7f1e4a67330490ea69ab58737c7' \ --header 'Content-Type: application/json' \ --data-raw '{ "id": "1", "jsonrpc": "2.0", "method": "eth_getBalance", "params": [ "0xF977814e90dA44bFA03b6295A0616a897441aceC", "latest" ] }' When I run this, I get the following response: {"jsonrpc":"2.0","id":"1","result":"0xa955677463bb9136d654"} The result field contains the balance of the wallet in HEX format. Automating Balance Checks with Python Now convert this into a Python script: import requests import json # Set the URL and headers url = "https://go.getblock.io/f7f1e4a67330490da69ab5bd9a9bf127" headers = { "Content-Type": "application/json" } # Set up the request payload payload = { "id": "1", "jsonrpc": "2.0", "method": "eth_getBalance", "params": [ "0xF977814e90dA44bFA03b6295A0616a897441aceC", "latest" ] } # Send the POST request response = requests.post(url, headers=headers, data=json.dumps(payload)) # Check if the request was successful if response.status_code == 200: # Convert the hex balance to decimal and then to Ether balance_wei = int(response.json()['result'], 16) balance_eth = balance_wei / 10**18 # Convert Wei to Ether print(f"The balance of the address is: {balance_eth} ETH") else: print(f"Error: {response.status_code}") print(response.text) Assuming you have Python installed on your machine and you have saved the above code in the file named balance.py, when you run this code it prints: The balance of the address is: 799655.3635441758 ETH. The balance_eth value matches the balance shown on Etherscan, proving the accuracy of this API. The balance_eth value matches the balance shown on Etherscan, proving the accuracy of this API. As you see, in just a few lines of code you can track a portfolio. From here, the possibilities are endless; you can come up with a notification system that will alert you how much your portfolio goes up or down, a custom dashboard that caters to your needs, and many other things. GetBlock made it easier and smooth. Closing thoughts To wrap things up, keeping track of token balances and wallet holdings is essential for managing digital assets effectively. GetBlock makes it easy for developers to access Ethereum’s blockchain data without the hassle of setting up and maintaining nodes. By using methods like eth_getBalance, you can quickly get real-time information on wallet balances, which helps stay on top of your assets. With GetBlock you can focus on building new features and applications. Whether you&amp;#8217;re working on dApps, managing a portfolio, or building financial tools, GetBlock’s straightforward API access makes interacting with the blockchain smooth and efficient. Starting from November 25, 2024, GetBlock is welcoming all its users to join the Black Friday promo event. Whether you’re a new or experienced user, GetBlock has something for you to offer. The last day to redeem this offer is Monday, December 2, 2024.&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/tracking-ethereum-wallet-balance-with-getblock-io/"&gt;Tracking Ethereum Wallet Balance with GetBlock.io&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Sun, 24 Nov 2024 17:55:35 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/tracking-ethereum-wallet-balance-with-getblock-io/</guid></item><item><title>A short introduction to Interval Tree Clocks</title><link>https://blog.separateconcerns.com/2017-05-07-itc.html</link><description>&lt;blockquote&gt;
&lt;p&gt;This post is a rough transcription of &lt;a href="http://files.catwell.info/presentations/2017-04-dotscale-itc/"&gt;a lightning talk I gave at dotScale
2017&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;EDIT (2024-11-24): this post is &lt;a href="https://news.ycombinator.com/item?id=42212543"&gt;on HN&lt;/a&gt; today.
For people finding this now, &lt;a href="https://blog.separateconcerns.com/2019-02-15-goodbye-lima.html"&gt;Lima has shut down&lt;/a&gt; since then.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;One of the things I work on at &lt;a href="https://blog.separateconcerns.com/2019-02-15-goodbye-lima.html"&gt;Lima&lt;/a&gt; is master-master
filesystem replication. In this kind of system, we need to
&lt;a href="http://queue.acm.org/detail.cfm?id=2917756"&gt;track causality&lt;/a&gt;. In a nutshell,
given two events modifying a given piece of data and originating from different
nodes in the system, we want to know if one of those events could have
influenced the other one, or in other words if one of those events
&lt;a href="https://en.wikipedia.org/wiki/Happened-before"&gt;“happened before”&lt;/a&gt; the other
one.&lt;/p&gt;
&lt;p&gt;To do that, we use constructs such as
&lt;a href="https://en.wikipedia.org/wiki/Version_vector"&gt;Version vectors&lt;/a&gt;. The idea is
that we give each node in the system a globally unique identifier, and we
associate it to a counter.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Version Vector" src="img/itc-vv.png" /&gt;&lt;/p&gt;
&lt;p&gt;When an event modifying data on a node occurs,
we increment the local value of the corresponding counter by one.&lt;/p&gt;
&lt;p&gt;&lt;img alt="inc" src="img/itc-vv-inc.png" /&gt;&lt;/p&gt;
&lt;p&gt;Version Vectors are partially ordered. Given two vectors, if we can find one
such that, for every node, its counter is higher than the other one, then
we say it descends the other one, meaning the related event “happened after”
the other one. Otherwise, we say that the vectors are concurrent, and typically
that means we will probably have some kind of data conflict to solve.&lt;/p&gt;
&lt;p&gt;&lt;img alt="compare" src="img/itc-vv-cmp.png" /&gt;&lt;/p&gt;
&lt;p&gt;When we merge data changes we also merge the vectors, and to do so we take
the maximum value of the counter for every node.&lt;/p&gt;
&lt;p&gt;&lt;img alt="merge" src="img/itc-vv-merge.png" /&gt;&lt;/p&gt;
&lt;p&gt;This works fine in most cases, but there is one case where it breaks down:
highly dynamic systems experiencing a lot of churn. This means systems where
nodes join the system, modify some data, then leave forever. The issue with
such systems is that, even though there may not be a lot of active devices at
any given point in time, the number of unique node identifiers in version
vectors keeps increasing. We call that issue actor explosion.&lt;/p&gt;
&lt;p&gt;Interval Tree Clocks are an attempt to solve this problem. Instead of
giving a unique identifier to every node in the system, we take the
real-valued interval &lt;code&gt;[0, 1]&lt;/code&gt; and attribute a part of it (not necessarily
contiguous) to every node. On top of it, we draw an integer-valued curve.
We call the combination of the interval share and the curve a stamp.&lt;/p&gt;
&lt;p&gt;&lt;img alt="stamp" src="img/itc-stamp.png" /&gt;&lt;/p&gt;
&lt;p&gt;To add a new node to the system, we start from an existing node and we fork it,
meaning we give part of its share of the interval to the new node.&lt;/p&gt;
&lt;p&gt;&lt;img alt="fork" src="img/itc-fork.png" /&gt;&lt;/p&gt;
&lt;p&gt;When an event occurs on a node, it increases the height of the curve of its
copy of the stamp anywhere within its share of the interval. Comparison works
similarly to Version Vectors: if the curve of a stamp is above the other one,
it descends it, otherwise the curves intersect and the stamps are concurrent.&lt;/p&gt;
&lt;p&gt;When a node wants to leave the system, it merges back with any other node and
surrenders its share of the interval. To merge, we just take the maximum
curve.&lt;/p&gt;
&lt;p&gt;&lt;img alt="join" src="img/itc-join.png" /&gt;&lt;/p&gt;
&lt;p&gt;The beauty of this scheme is that a node only has to know about its share of
the interval, not information about all other nodes. There are no globally
unique node identifiers.&lt;/p&gt;
&lt;p&gt;If we choose how we increase the height of the curve when an event occurs in a
clever way which I will not detail here, we can ensure the complexity of the
curve remains low. We can then encode it efficiently using a tree-shaped data
structure and a custom binary format, with a size that depends more on the
number of nodes interacting with the data at a given point in time than
on the overall number of nodes which have touched it since inception.&lt;/p&gt;
&lt;p&gt;&lt;img alt="results" src="img/itc-results.png" /&gt;&lt;/p&gt;
&lt;p&gt;If you want to know more, I encourage you to read
&lt;a href="https://gsd.di.uminho.pt/members/cbm/ps/itc2008.pdf"&gt;the 2008 paper&lt;/a&gt; by Paulo Sérgio
Almeida, Carlos Baquero and Victor Fonte; it is one of the best I know on the
topic of causality. You can also check out &lt;a href="https://github.com/catwell/cw-lua/tree/master/itc.lua"&gt;my Lua implementation of
ITC&lt;/a&gt; or one of the other implementations
linked in the README.&lt;/p&gt;
&lt;p&gt;EDIT (2024-11-24): other interesting resources are &lt;a href="https://cbaquero.github.io/web/pdf/SDLtime2021.pdf"&gt;this slide deck by Carlos Baquero&lt;/a&gt;
and &lt;a href="https://ferd.ca/interval-tree-clocks.html"&gt;those&lt;/a&gt; &lt;a href="https://ferd.ca/a-bridge-over-a-river-never-crossed.html"&gt;posts&lt;/a&gt; by Fred Hebert.&lt;/p&gt;</description><author>Separate Concerns</author><pubDate>Sun, 24 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.separateconcerns.com/2017-05-07-itc.html</guid></item><item><title>Platform-Specific Resources in SwiftPM</title><link>https://defn.io/2024/11/24/swiftpm-platform-specific-resources</link><description>&lt;article&gt;&lt;p&gt;&lt;a href="https://github.com/Bogdanp/Noise"&gt;Noise&lt;/a&gt; packages Racket &amp;amp; Chez Scheme boot files&lt;sup&gt;&lt;a href="#fn_1" id="fnref_1_1"&gt;1&lt;/a&gt;&lt;/sup&gt; for all the
platforms it supports. Originally, that was just &lt;code&gt;x86-64&lt;/code&gt; and &lt;code&gt;arm64&lt;/code&gt;
macOS, but when I added iOS support, that extended to include &lt;code&gt;arm64&lt;/code&gt;
iOS. These boot files take up about 45MB for each &lt;code&gt;arch+os&lt;/code&gt; pair and
the way I originally distributed them was placing them all in a &lt;code&gt;boot&lt;/code&gt;
folder and adding them to the &lt;code&gt;resources&lt;/code&gt; list for the core &lt;code&gt;Noise&lt;/code&gt;
&lt;a href="https://github.com/Bogdanp/Noise/blob/0581556c6977948d85839c589a1079e53f2368f5/Package.swift#L31-L33"&gt;target&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;That worked fine, but it seemed like a waste to lug around an extra 90MB
of data that would never be used inside my iOS apps. Looking at the
&lt;a href="https://developer.apple.com/documentation/packagedescription"&gt;&lt;code&gt;PackageDescription&lt;/code&gt;&lt;/a&gt; docs, there doesn't appear to be a way to filter
resources by platform. There is an &lt;code&gt;exclude&lt;/code&gt; property on &lt;code&gt;Target&lt;/code&gt;s,
which I tried to use by making the &lt;code&gt;Package.swift&lt;/code&gt; script manipulate the
target at runtime, but I couldn't figure out a way to get that working
with cross-compilation.&lt;/p&gt;&lt;p&gt;Next, I tried writing a SwiftPM &lt;a href="https://github.com/swiftlang/swift-package-manager/blob/dca0cc27b9d5f08a9c9a38101e322d0f3ab1ba03/Documentation/Plugins.md#implementing-the-build-tool-plugin-script"&gt;build tool plugin&lt;/a&gt; to remove files
based on the target platform, but plugin execution is sandboxed and I
couldn't figure out a way to move the boot files out of the package
context&lt;sup&gt;&lt;a href="#fn_2" id="fnref_2_1"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;&lt;p&gt;Finally, I &lt;a href="https://github.com/Bogdanp/Noise/commit/4fb9fccc84a583b0bf8536063d63ad3aed84bb6c#diff-f913940c58e8744a2af1c68b909bb6383e49007e6c5a12fb03104a9006ae677eR25-R32"&gt;settled on&lt;/a&gt; just making separate targets for macOS
and iOS. Each target contains its respective boot files and the main
target conditionally depends on the platform-specific targets. I really
wanted to avoid this approach because it's somewhat ugly and it means I
have to manually wire things around that the build system should be able
to do for me, but, for now, this seems like the most sensible approach.&lt;/p&gt;&lt;section class="footnotes"&gt;&lt;ol&gt;&lt;li id="fn_1"&gt;&lt;p&gt;Object files that contain the Racket and Chez Scheme runtime. &lt;a class="footnote-backref" href="#fnref_1_1" title="Jump to reference"&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li id="fn_2"&gt;&lt;p&gt;As I'm writing this, I wonder if I could've combined the &lt;code&gt;exclude&lt;/code&gt;
property and a build tool plugin to define a folder into which I
could've moved the unneeded boot files during the build. &lt;a class="footnote-backref" href="#fnref_2_1" title="Jump to reference"&gt;↩&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/section&gt;&lt;/article&gt;</description><author>defn.io</author><pubDate>Sun, 24 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://defn.io/2024/11/24/swiftpm-platform-specific-resources</guid></item><item><title>Hope Of Return</title><link>https://sudhar.xyz/2024/11/24/hope-of-return.html</link><description>The scent of grandmother’s kitchen still lingers in my dreams. Cardamom and cinnamon, the steam rising from the pot as she stirred, her silver bangles jingling with each movement. I was ten when I last saw that kitchen, when I last sat on that worn wooden stool watching her cook.</description><author>ST</author><pubDate>Sun, 24 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://sudhar.xyz/2024/11/24/hope-of-return.html</guid></item><item><title>Macro and Microphenomenology</title><link>https://superbowl.substack.com/p/macro-and-microphenomenology</link><description>How to study the dynamics of the mind</description><author>Superb Owl</author><pubDate>Sat, 23 Nov 2024 19:59:00 GMT</pubDate><guid isPermaLink="true">https://superbowl.substack.com/p/macro-and-microphenomenology</guid></item><item><title>Highlighted code in slides</title><link>https://fasterthanli.me/articles/highlighted-code-in-slides</link><description>&lt;p&gt;I have obsessed about this long enough, I think it’s only fair I (and you!) get some content out of it.&lt;/p&gt;

&lt;p&gt;When I started writing this article, I was working on my &lt;a href="https://p99conf.io"&gt;P99 CONF&lt;/a&gt; slides.
Those slides happen to include some bits of code. And because I’m a perfectionist, I would like this
code to be syntax highlighted, like this:&lt;/p&gt;

&lt;figure class="code-block has-language-tag"&gt;&lt;span class="language-tag" title="Rust"&gt;&lt;/span&gt;&lt;code class="scroll-wrapper"&gt;let addr: SocketAddr = config.address.parse()?;
let ln = TcpListener::bind(addr?
 config
&lt;/code&gt;&lt;/figure&gt;






&lt;a class="anchor" href="#not-invented-here" id="not-invented-here"&gt;&lt;/a&gt;






















&lt;!-- playwall --&gt;














&lt;a class="anchor" href="#adding-a-button" id="adding-a-button"&gt;&lt;/a&gt;














































&lt;a class="anchor" href="#computed-styles" id="computed-styles"&gt;&lt;/a&gt;















































&lt;a class="anchor" href="#code-listing" id="code-listing"&gt;&lt;/a&gt;






&lt;a class="anchor" href="#color-is-hard" id="color-is-hard"&gt;&lt;/a&gt;










































&lt;a class="anchor" href="#closing-thoughts" id="closing-thoughts"&gt;&lt;/a&gt;</description><author>fasterthanli.me</author><pubDate>Sat, 23 Nov 2024 19:40:00 GMT</pubDate><guid isPermaLink="true">https://fasterthanli.me/articles/highlighted-code-in-slides</guid></item><item><title>French Toast</title><link>https://sam.hooke.me/recipe/french-toast/</link><description>&lt;h2 id="ingredients"&gt;Ingredients&lt;/h2&gt;
&lt;p&gt;For two people (four pieces, two per person):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For the liquid:
&lt;ul&gt;
&lt;li&gt;2 large eggs.&lt;/li&gt;
&lt;li&gt;120g of milk.&lt;/li&gt;
&lt;li&gt;15g of plain flour.&lt;/li&gt;
&lt;li&gt;25g of granulated sugar.&lt;/li&gt;
&lt;li&gt;A pinch of salt.&lt;/li&gt;
&lt;li&gt;1 tsp ground cinnamon.&lt;/li&gt;
&lt;li&gt;1 tsp vanilla extract.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For frying:
&lt;ul&gt;
&lt;li&gt;Butter.&lt;/li&gt;
&lt;li&gt;4 slices of bread (thick but not too thick, preferably a day or two old).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="method"&gt;Method&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Mix all ingredients for the liquid.&lt;/li&gt;
&lt;li&gt;Heat up a frying pan with some butter.&lt;/li&gt;
&lt;li&gt;Pour the liquid into a dish, then lay two pieces of bread in the dish to soak up the liquid, then turn so the other side soaks them up.&lt;/li&gt;
&lt;li&gt;Place the two slices on the fying pan and cook for a few minutes on each side until golden.&lt;/li&gt;
&lt;li&gt;Repeat for the other slides of bread.&lt;/li&gt;
&lt;/ol&gt;</description><author>Sam Hooke</author><pubDate>Sat, 23 Nov 2024 11:30:00 GMT</pubDate><guid isPermaLink="true">https://sam.hooke.me/recipe/french-toast/</guid></item><item><title>Foundations of High-Performance Engineering Teams</title><link>https://blog.herlein.com/post/foundations-excellence/</link><description>&lt;p&gt;The US Navy gave me a solid foundation in leadership and operational excellence.  But translating that into the business world requires a few more things.&lt;/p&gt;</description><author>Greg Herlein</author><pubDate>Sat, 23 Nov 2024 10:00:01 GMT</pubDate><guid isPermaLink="true">https://blog.herlein.com/post/foundations-excellence/</guid></item><item><title>Making money from free content</title><link>https://ilearnt.com/blog/freecontent/</link><description>&lt;p&gt;Sam Harris gives away his content for free but still makes money.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Sat, 23 Nov 2024 09:52:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/freecontent/</guid></item><item><title>Interview: Wie Atlassian auf Cloudangst und Überkonfiguration seiner Tools reagiert</title><link>https://backendhance.com/blog/2024/atlassian-about-cloud-anxiety-and-overconfiguration/</link><description>&lt;p&gt;&lt;em&gt;Ich war kürzlich auf der Team ‘24, quasi Atlassians Hausmesse, und habe mich dort mit Matt Schvimmer, Senior Vice President und Head of Product für das Agile- und Devops-Portfolio von Atlassian, getroffen. Wir haben darüber gesprochen, welche Unterstützung die Atlassian-Tools für moderne Softwareentwicklung bieten.&lt;/em&gt;&lt;/p&gt;
&lt;div class="alert d-flex alert-info"&gt;
&lt;i class="bx bx-info-circle lead me-3"&gt;&lt;/i&gt;
&lt;div&gt;
Dieses Interview erschien erstmals im &lt;a href="https://archive.newsletter2go.com/?n2g=k78op17b-x3y81agx-2ck" rel="noopener noreferrer" target="_blank"&gt;Chef von Devs Newsletter #57&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;&lt;em&gt;Besonders für Teams, die in komplexen Umgebungen arbeiten, spielen Jira, Bitbucket, Compass und das neu angekündigte Focus eine große Rolle. Schvimmer hat mir Einblicke in die Weiterentwicklung der Atlassian-Tools und in zukünftige Pläne gegeben. Außerdem sprach er mit mir über Herausforderungen wie die Synchronisation von Dokumentation mit dem Code und den Umgang mit der Komplexität von Microservices. Aber auch die Überkonfiguration von Tools oder Bedenken gegenüber der Cloud waren im Interview Thema.&lt;/em&gt;&lt;/p&gt;</description><author>Backendhance</author><pubDate>Sat, 23 Nov 2024 09:47:27 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2024/atlassian-about-cloud-anxiety-and-overconfiguration/</guid></item><item><title>Deploy a Node.js app on EC2</title><link>https://hsnice16.medium.com/deploy-a-node-js-app-on-ec2-06b09d188f5f?source=rss-7c3a23f073ae------2</link><description>&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*R2sJS-SPrEL_x4RWNlEmuw.png" /&gt;&lt;/figure&gt;&lt;p&gt;Hello everyone 👋,&lt;br /&gt;My name is Himanshu Singh. I am an Engineer, and after working as a front-end engineer for over two years, I started doing full-stack.&lt;/p&gt;&lt;p&gt;Recently, I did a small side gig, where I had to build a price change tracker for tokens on Solana.&lt;/p&gt;&lt;p&gt;I have written a blog explaining what I did in that.&lt;/p&gt;&lt;p&gt;In this blog, I wanted to write how I deployed the Node.js app I built in that gig. So that, you know how you can do the same as well.&lt;/p&gt;&lt;p&gt;Let’s begin!&lt;/p&gt;&lt;blockquote&gt;The blog I wrote — &lt;a href="https://hsnice16.medium.com/track-token-price-change-in-solana-7d6dbd363c69"&gt;https://hsnice16.medium.com/track-token-price-change-in-solana-7d6dbd363c69&lt;/a&gt;&lt;/blockquote&gt;&lt;h3&gt;Launch a new EC2 instance&lt;/h3&gt;&lt;p&gt;First, we will start with launching a new instance, that you can do from the EC2 dashboard.&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*stZeNscPTe2Q5ksOLJlQug.png" /&gt;&lt;/figure&gt;&lt;p&gt;After clicking on that, you will be redirected to a Launch page, where you will have to fill in the details of the instance, like its name, OS, type, and other things. For learning, you can choose free tier options.&lt;/p&gt;&lt;p&gt;Like, for OS, you can choose &lt;em&gt;Ubuntu.&lt;/em&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*n-xCurg_qeTxbQk10h_HRQ.png" /&gt;&lt;/figure&gt;&lt;p&gt;For Instance type, you can keep the default free tier selected&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*BkvBFzEwNki26NG7ysnF_A.png" /&gt;&lt;/figure&gt;&lt;p&gt;The key pair field is also required, so you will have to fill that. It’s used to securely connect to your EC2 instance.&lt;/p&gt;&lt;p&gt;After filling in all the details, on the right-hand side, you can click on &lt;em&gt;“Launch Instance”&lt;/em&gt;.&lt;/p&gt;&lt;p&gt;After that is successful, you can go to the Instances dashboard by clicking on it under the Instances dropdown on the left-hand side menu, select the instance you created, and click on Connect in the top menu.&lt;/p&gt;&lt;p&gt;You can keep the selected option as it is and click on the Connect button, which will connect you to your EC2 instance on Web UI.&lt;/p&gt;&lt;h3&gt;Install dependencies&lt;/h3&gt;&lt;p&gt;EC2 instance is just like a computer running on the cloud. So, the way you will clone and run the app locally, you will do the same thing in the EC2 instance as well.&lt;/p&gt;&lt;p&gt;As we know, to run a Node.js app, we need to install Node first on our computer, we will have to do the same in the instance. So, let’s first install the Node.&lt;/p&gt;&lt;p&gt;AWS team has already created a document on how you can install Node on an EC2 instance, so you can check the &lt;em&gt;“To set up Node.js on your Linux instance”&lt;/em&gt; section in that — &lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html"&gt;https://docs.aws.amazon.com/sdk-for-javascript/v2/developer-guide/setting-up-node-on-ec2-instance.html&lt;/a&gt;&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*baAxH-jNAOp0SdZOnKkWFQ.png" /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*AUcC2Q3grqpbPvtr4oXvWA.png" /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*tuu7KCNzSShOFyue8a54Fg.png" /&gt;&lt;/figure&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/728/1*GBlOL4jFMj_4OnK4QzdbDg.png" /&gt;&lt;/figure&gt;&lt;p&gt;After that, we will install Git. And, to do that, you can use the sudo apt-get install git command, and can also check the Git installation guide if you face any issues — &lt;a href="https://git-scm.com/book/en/v2/Getting-Started-Installing-Git"&gt;https://git-scm.com/book/en/v2/Getting-Started-Installing-Git&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;We need one more dependency to deploy our app.&lt;/strong&gt;&lt;br /&gt;So, locally when you start your server and then if the computer sleeps after some time of inactivity, the server stops as well. And, the same is the case with the EC2 instance, so to keep our app running forever, we will need to install the pm2 library — &lt;a href="https://www.npmjs.com/package/pm2"&gt;https://www.npmjs.com/package/pm2&lt;/a&gt;&lt;/p&gt;&lt;p&gt;To do that, you can use npm install pm2 -g&lt;/p&gt;&lt;h3&gt;Start the app&lt;/h3&gt;&lt;p&gt;We have installed all the dependencies we will gonna need, so it’s time to start our app.&lt;/p&gt;&lt;p&gt;First, we can clone the repository from the GitHub. And, one way to do that is by using the git clone https://github.com/hsnice16/alert-on-price-change.git command.&lt;/p&gt;&lt;p&gt;After that, we can follow the project packages installation guide mentioned in the README file.&lt;/p&gt;&lt;p&gt;And, in the last, to run the app, and keep it running we can use the command pm2 start index.js (note that we don’t need node index.js).&lt;/p&gt;&lt;figure&gt;&lt;img alt="" src="https://cdn-images-1.medium.com/max/1024/1*SbmcKpnB8FbAbzaEVECFeA.png" /&gt;&lt;/figure&gt;&lt;h3&gt;That’s all&lt;/h3&gt;&lt;p&gt;Thank you for reading it till the end.&lt;br /&gt;I am hoping that after going through this blog, you will be able to deploy your app on EC2.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Let’s connect on &lt;/em&gt;&lt;a href="https://twitter.com/hsnice16"&gt;&lt;em&gt;Twitter (Now X)&lt;/em&gt;&lt;/a&gt;&lt;em&gt;.&lt;/em&gt;&lt;/p&gt;&lt;blockquote&gt;You can find the gig project link on my GitHub — &lt;a href="https://github.com/hsnice16/alert-on-price-change"&gt;https://github.com/hsnice16/alert-on-price-change&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;If you found this blog and this project helpful, you can also sponsor me on GitHub — &lt;a href="https://github.com/sponsors/hsnice16"&gt;https://github.com/sponsors/hsnice16&lt;/a&gt;&lt;/blockquote&gt;&lt;img alt="" height="1" src="https://medium.com/_/stat?event=post.clientViewed&amp;amp;referrerSource=full_rss&amp;amp;postId=06b09d188f5f" width="1" /&gt;</description><author>Stories by Himanshu Singh on Medium</author><pubDate>Sat, 23 Nov 2024 08:03:45 GMT</pubDate><guid isPermaLink="true">https://hsnice16.medium.com/deploy-a-node-js-app-on-ec2-06b09d188f5f?source=rss-7c3a23f073ae------2</guid></item><item><title>Wisps</title><link>https://robkohr.com/articles/wisps</link><description>Wisps</description><author>RobKohr's Blog</author><pubDate>Sat, 23 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/wisps</guid></item><item><title>Making SQL Keyword Suggestions Work</title><link>https://xnacly.me/posts/2024/making-sql-keyword-suggestions-work/</link><description>Implementing levenshtein distance naïvely (and very slow) in Rust</description><author>xnacly - blog</author><pubDate>Sat, 23 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2024/making-sql-keyword-suggestions-work/</guid></item><item><title>Eng Log 0001</title><link>https://mehulkar.com/blog/2024/11/eng-log-0001?utm_source=rss</link><description>&lt;p&gt;One of the biggest reasons I don't write more often is because I have trouble
figuring out what to call it. But I want to make more room for just writing
things and worrying less about form. So instead of coming up with a title, I'm
just going to index it by a number. I've seen other writers do this also.
Normally at this point I would try to find evidence of these other writers and
reference them and their thoughts about this style, but I'm shedding that burden
too. So anyway, the reason I made these decisions is to write down a thought I
had. &lt;code&gt;/preamble&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Last week, some folks from Sentry reached out about a flakey test detection
product. My team owns our backend codebase and flakey tests had been a problem
recently, so our CTO put us in touch with these nice folks.&lt;/p&gt;
&lt;p&gt;We don't really see a lot of flakey tests in our CI anymore. But I realized the
reason for that is that we fixed a totally orthogonal problem: our package
graph. We have a large monorepo that uses &lt;code&gt;turbo&lt;/code&gt; to determine which tests to
run based on the files changed in each pull request.&lt;/p&gt;
&lt;p&gt;Previously, almost every pull request invalidated all 500 packages in the
monorepo, which meant that every test would have to run. When we started
tracking this data, we saw that ~60% of CI runs had to run the whole suite.
&lt;em&gt;After&lt;/em&gt; we fixed our package graph, that's down to 3% of CI runs. That means
that many tests are just not running as frequently now. There are a few reasons
this means that &amp;quot;flakey tests aren't a problem&amp;quot; anymore. Because fewer tests
run,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;we just don't run into the problem as much, all else being the same&lt;/li&gt;
&lt;li&gt;flakiness due to resource contention (e.g. timing problems) is gone&lt;/li&gt;
&lt;li&gt;the likelihood of tests that only fail in certain orders and combinations is reduced&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So for the most part, we haven't fixed flaky tests, but by fixing a different
problem, we changed how often it surfaces, and how much we need to care about it
right now.&lt;/p&gt;
&lt;p&gt;This is interesting in the world of engineering and project management, because
it's hard to communicate the relationship between two seemingly unrelated
problems. If we had done a better job of communicating the impact of a bad
package graph and the effect of fixing it, it's possible that we'd never even
have been introduced to Sentry to take this call.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/end&lt;/code&gt;&lt;/p&gt;

        &lt;p&gt;Thanks for reading this post via RSS!&lt;/p&gt;</description><author>Mehul Kar's blog</author><pubDate>Sat, 23 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://mehulkar.com/blog/2024/11/eng-log-0001?utm_source=rss</guid></item><item><title>2024-11-23</title><link>https://ho.dges.online/pictures/2024-11-23/</link><description/><author>ho.dges.online</author><pubDate>Sat, 23 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-11-23/</guid></item><item><title>Dear friend, you have built a Kubernetes</title><link>https://www.macchaffee.com/blog/2024/you-have-built-a-kubernetes/</link><description>&lt;p&gt;&lt;em&gt;This post will make more sense if you first read &lt;a href="https://rachit.pl/post/you-have-built-a-compiler/"&gt;Dear Sir, You Have Built a Compiler&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Dear friend,&lt;/p&gt;
&lt;p&gt;I am afraid to inform you that you have built a Kubernetes. I know you wanted to "choose boring tech" to just run some containers. You said that "Kubernetes is overkill" and "it's just way too complex for a simple task" and yet, six months later, you have a pile of shell scripts that do not work—breaking every time there's a slight shift in the winds of production.&lt;/p&gt;
&lt;p&gt;Surely, switching to Docker Compose will be the end of your woes; at least that way, someone else maintains a standard config file format for you to use. But wait, Compose is still &lt;a href="https://www.macchaffee.com/blog/2024/docker-compose/"&gt;not a holistic solution&lt;/a&gt;. "Do I really need a separate solution for deployment, rolling updates, rollbacks, and scaling?" you ask yourself? Surely not. Your app is so simple; a backend, a reverse proxy, postgres, and a job runner. So you march on, and add another few sections to your &lt;code&gt;deploy.sh&lt;/code&gt; script, certain, that this will be the last of what you need to do to maintain this pile of hacks.&lt;/p&gt;
&lt;p&gt;Ah, but wait! Inevitably, you find a reason to expand to a second server. While it's true &lt;a href="https://news.ycombinator.com/item?id=41340751"&gt;a single server can go a long way&lt;/a&gt; many things can force this decision, such as the need for special hardware, high availability, or the speed of light. Tired, you parameterize your deploy script and configure firewall rules, distracted from the crucial features you should be working on and shipping. One of your team members suggests connecting the servers with Tailscale: an overlay network with service discovery. After that you will know, yes know, that there is no tougher complexity hurdle to clear than the networking.&lt;/p&gt;
&lt;p&gt;Except if you quit or go on vacation, who will maintain this custom pile of shell scripts? The untested tarball handling? The inscrutable iptables rules? Who will know about those undocumented sysctl edits you made on the VM? So you add everything to Ansible, so that you may treat your VM as immutable and version-controlled. Certain, of course, that because you’re not using Kubernetes, it is going to be way easier to maintain than a Kubernetes cluster. What glorious engineering, you say to yourself.&lt;/p&gt;
&lt;p&gt;In the last leg of your journey to avoid building a Kubernetes, your manager tells you that your app needs to programmatically spawn other containers. Spawning containers, of course, requires you to mount the Docker socket in your web app, which is wildly insecure. Not my problem, your manager says. So you write a separate service that exposes a safe subset of the Docker API to your web app. Done at last, you say to yourself, without having to build a Kubernetes.&lt;/p&gt;
&lt;p&gt;A standard config format, a deployment method, an overlay network, service discovery, immutable nodes, and an API server. Dear friend, you have built a Kubernetes.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Addressed to,&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Those who wanted to avoid Kubernetes.&lt;/em&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;em&gt;PS: I don't mean to imply that you can never roll your own deployment method that fits your needs better than Kubernetes, or that nothing will ever be better than Kubernetes. I just want to caution you, my friend, to make sure you understand the problems Kubernetes solves before dismissing it as overly-complex.&lt;/em&gt;&lt;/p&gt;</description><author>Mac's Tech Blog</author><pubDate>Sat, 23 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.macchaffee.com/blog/2024/you-have-built-a-kubernetes/</guid></item><item><title>The Diplomat And Reality</title><link>https://sudhar.xyz/2024/11/23/the-diplomat-and-reality.html</link><description>Britain’s Diplomatic Delusions: Netflix vs Reality</description><author>ST</author><pubDate>Sat, 23 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://sudhar.xyz/2024/11/23/the-diplomat-and-reality.html</guid></item><item><title>Mgmt Configuration Language: Functions</title><link>https://purpleidea.com/blog/2024/11/22/functions-in-mgmt/</link><description>&lt;p&gt;It&amp;rsquo;s been a little while since I introduced the
&lt;a href="https://purpleidea.com/blog/2018/02/05/mgmt-configuration-language/"&gt;Mgmt Configuration Language&lt;/a&gt;. I
originally wrote this article in 2019, and as I was working on it alongside the
code for functions, when I realized that lambdas didn&amp;rsquo;t work properly. It took
some time to finally solve that properly. Since then I&amp;rsquo;ve been working to get &lt;a href="https://github.com/purpleidea/mgmt/"&gt;mgmt&lt;/a&gt;
production ready. It&amp;rsquo;s properly useful for production now, and so it&amp;rsquo;s time for
me to catch up on my documentation. I&amp;rsquo;ve revived and updated this article, let&amp;rsquo;s
go&amp;hellip;&lt;/p&gt;</description><author>The Technical Blog of James on purpleidea.com</author><pubDate>Sat, 23 Nov 2024 01:16:00 GMT</pubDate><guid isPermaLink="true">https://purpleidea.com/blog/2024/11/22/functions-in-mgmt/</guid></item><item><title>IdentityServer in Docker Containers – Part 1</title><link>https://nestenius.se/net/identityserver-in-docker-containers-part-1/</link><description>&lt;p&gt;Getting Duende IdentityServer and a client application up and running in separate containers can be challenging. This blog post will provide a step-by-step guide for a smooth setup and show you how to resolve common challenges along the way. We will also learn about security, cookies, ports, containers, and certificates. This is a big topic, [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/net/identityserver-in-docker-containers-part-1/"&gt;IdentityServer in Docker Containers &amp;#8211; Part 1&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Fri, 22 Nov 2024 16:31:11 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/net/identityserver-in-docker-containers-part-1/</guid></item><item><title>A Walk-Through of String Search Algorithms</title><link>https://photonlines.substack.com/p/a-walk-through-of-string-search-algorithms</link><description>A walk-through of how some of the popular string-search algorithms work with a focus on the well-known Boyer-Moore and Robin-Karp algorithms.</description><author>Photon-Lines Substack</author><pubDate>Fri, 22 Nov 2024 16:23:30 GMT</pubDate><guid isPermaLink="true">https://photonlines.substack.com/p/a-walk-through-of-string-search-algorithms</guid></item><item><title>OnlineOrNot Diaries 22</title><link>https://maxrozen.com/onlineornot-diaries-22</link><description>Feels like I've already said everything I had to say</description><author>Max Rozen</author><pubDate>Fri, 22 Nov 2024 09:10:00 GMT</pubDate><guid isPermaLink="true">https://maxrozen.com/onlineornot-diaries-22</guid></item><item><title>Toggle macOS menu bar from you know where</title><link>https://xenodium.com/toggle-macos-menu-bar-from-you-know-where</link><description>&lt;p&gt;I'm a fan of macOS's auto-hide menu bar setting. Unless I'm reaching out to a menu item, I don't typically need to have a visible menu bar, so I set auto-hide to &amp;quot;Always&amp;quot;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/toggle-macos-menu-bar-from-you-know-where/control-center.png" /&gt;&lt;/p&gt;
&lt;p&gt;On rare occasions, I turn this setting off (say &lt;a href="https://lmno.lol/alvaro/chatgpt-shell-goes-offline"&gt;for a screen grab&lt;/a&gt;). While reaching out to macOS Control Center is OK, I wanted to quickly toggle it from the comfort of Emacs via &lt;code&gt;M-x&lt;/code&gt; fuzzy search.&lt;/p&gt;
&lt;p&gt;We can leverage AppleScript to toggle the setting on and off. In the past, I would write shell scripts for this kinda thing and invoke from the command line. These days, I dump them into a &lt;a href="https://github.com/xenodium/dwim-shell-command"&gt;dwim-shell-command&lt;/a&gt; and quickly forget about its implementation. From then on, I just M-x and fuzzy search for some magical incantation (I'm looking at you ffmpeg). I got &lt;a href="https://github.com/xenodium/dwim-shell-command?tab=readme-ov-file#my-toolbox"&gt;a bunch of these things&lt;/a&gt;…&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun dwim-shell-commands-macos-toggle-menu-bar-autohide ()
  &amp;quot;Toggle macOS menu bar auto-hide.&amp;quot;
  (interactive)
  (dwim-shell-command-on-marked-files
   &amp;quot;Toggle menu bar auto-hide.&amp;quot;
   &amp;quot;current_status=$(osascript -e 'tell application \&amp;quot;System Events\&amp;quot; to get autohide menu bar of dock preferences')

if [ \&amp;quot;$current_status\&amp;quot; = \&amp;quot;true\&amp;quot; ]; then
    osascript -e 'tell application \&amp;quot;System Events\&amp;quot; to set autohide menu bar of dock preferences to false'
    echo \&amp;quot;Auto-hide disabled.\&amp;quot;
else
    osascript -e 'tell application \&amp;quot;System Events\&amp;quot; to set autohide menu bar of dock preferences to true'
    echo \&amp;quot;Auto-hide enabled.\&amp;quot;
fi&amp;quot;
   :utils &amp;quot;osascript&amp;quot;
   :silent-success t))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;…and that's all there is to it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/toggle-macos-menu-bar-from-you-know-where/autohide.webp" /&gt;&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Fri, 22 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/toggle-macos-menu-bar-from-you-know-where</guid></item><item><title>Warlock AI</title><link>https://er4hn.info/blog/2024.11.22-warlock-ai/</link><description>Then Life Continued</description><author>er4hn</author><pubDate>Fri, 22 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://er4hn.info/blog/2024.11.22-warlock-ai/</guid></item><item><title>Goodbye</title><link>https://nutcroft.mataroa.blog/blog/goodbye/</link><description>&lt;p&gt;I had been struggling to write for many years. Back then I was dreaming of having a blog with lots of entries. Now I do. I admired the people who did and now I'm one of them.&lt;/p&gt;
&lt;p&gt;The feeling now is: this blog is so old. It's ten years old. I am definitely not the same person I was then. Most of these texts no longer reflect my opinions and thoughts. I’m probably ashamed for most of them (so don't read them!).&lt;/p&gt;
&lt;p&gt;But that's ok, isn't it? &lt;a href="https://en.wikipedia.org/wiki/Impermanence_(Buddhism)"&gt;Everything comes and goes&lt;/a&gt;. And now this blog goes too.&lt;/p&gt;
&lt;p&gt;So, &lt;a href="https://youtu.be/JuANQaHcsH0?t=44"&gt;goodbye&lt;/a&gt;!&lt;/p&gt;</description><author>nutcroft</author><pubDate>Fri, 22 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nutcroft.mataroa.blog/blog/goodbye/</guid></item><item><title>On Music Tabs</title><link>https://zserge.com/posts/tab/</link><description>I love music. I love playing music. I love making musical instruments and music software. I don&amp;rsquo;t have formal musical training. I’ve never taken lessons, and honestly, I’m a pretty bad musician. What I do excel at, though, is hoarding musical instruments.
At this point, name any instrument, and there’s a decent chance I have it somewhere in my room. Some of these instruments never played anything better than &amp;ldquo;Twinkle, Twinkle Little Star&amp;rdquo;, others featured in a recording or two, and only a few see regular play.</description><author>zserge's blog</author><pubDate>Fri, 22 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://zserge.com/posts/tab/</guid></item><item><title>ConvoCards Launch Retro</title><link>https://briansunter.com/convocards-launch-retro</link><description>Thoughts and learnings from launching a new app.</description><author>Brian Sunter</author><pubDate>Fri, 22 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://briansunter.com/convocards-launch-retro</guid></item><item><title>Workload</title><link>https://mschaef.com/workload</link><description>&lt;p&gt;Lately, I've been reading &lt;a href="https://www.amazon.com/Beyond-Mach-Pilots-Journey-Through-ebook/dp/B0D8VP37C8"&gt;"Beyond Mach 3"&lt;/a&gt;), Col. Buddy Brown's memoir of his years piloting the &lt;a href="https://en.wikipedia.org/wiki/Lockheed_U-2"&gt;U-2&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Lockheed_SR-71_Blackbird"&gt;SR-71&lt;/a&gt; spy planes. If you like that sort of thing, it's an interesting read on its own, but it also has parallels to the software industry, particularly in the way it describes pilot workload. The concept of pilot workload provides a nice way to think explicitly about the burdens our systems place on us as engineers and operators to keep them running properly.&lt;/p&gt;&lt;p&gt;If you're not familar with the U-2, it's a 1950's era aircraft designed to overfly hostile territory and take high resolution pictures of the ground for later analysis. It was a U-2 that flew over Cuba to take the pictures of Soviet missiles at the beginning of the &lt;a href="https://www.jfklibrary.org/learn/about-jfk/jfk-in-history/cuban-missile-crisis"&gt;Cuban Missile Crisis&lt;/a&gt;. To make these sorts of missions possible, the plane was designed to fly at very high altitudes, upwards of 70,000 feet. This is almost double the altitude of a modern passenger airliner.&lt;/p&gt;&lt;p&gt;What may not be obvious is that airplanes get progressively more difficult to fly at higher altitudes.  If speed drops below the stall speed, the wings stall and lose lift. If speed gets too high, the airflow separates and the wings lose lift. Either way, if the speed deviates out of this window, the pilot loses control entirely. Due to the decreasing density of the atmosphere at higher altitudes, this window gets narrower as the plane flies higher. Fly high enough, the window closes entirely, and you've just discovered the ceiling of your aircraft. It can't fly that high at all.&lt;/p&gt;&lt;p&gt;A U-2 flying at 70,000 feet is flying very close to its ceiling. The range of permissable speeds at this point in its flight is just 5-10mph wide. Regardless of what else is going on, the pilot has to be completely precise managing their speed in order to retain control. This region of a flight envelope is referred to as the &lt;a href="https://en.wikipedia.org/wiki/Coffin_corner_(aerodynamics"&gt;coffin corner&lt;/a&gt;), due to the catastrophic implications of a mistake.&lt;/p&gt;&lt;p&gt;On top of the work required to keep the plane in the air, the pilot of a U-2 is also taking celesital navigation readings every 30 minutes, compensating for the curvature of the earth, and running the sensor packages that are the point of the mission in the first place. In the best case, a mistake means the mission doesn't produce the desired results. In the worst case, a mistake means a crash in hostile territory and an international incident.  To put it plainly, the U-2 asked a lot of its pilot as part of its basic operation. In aerospace engineering, this is referred to as having a high &lt;a href="https://www.aviationfile.com/understanding-pilot-workload-keeping-pilots-focused-and-safe/"&gt;pilot workload&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;For the U-2 program, this was arguably an appropriate design choice. The program had the ability to be highly selective in the pilots it chose, it had the ability to engage in extensive and costly training of those pilots, and it was operating at the limits of available technology.  This was the only way their mission requirements could be satisfied at the time, so they were forced into a design that placed high workload demands on the pilot. As soon as technology got better, it became possible to fullfill many of the same requirements with less likelihood of error and overall lower risk, which is what happened.&lt;/p&gt;&lt;p&gt;There are a few lessons in this for software professionals. While most commercial software systems don't overfly hostile enemy territory, they hopefully do have tangible real world implications anyway. Otherwise, what's the point of building software at all? The reality is that if your software matters, operational failures will have tangible real-world impacts and costs. This means that to the extent economically possible, we have to get it right. This is where the workload concept can play a role.&lt;/p&gt;&lt;p&gt;In the context of aerospace engineering, workload refers to the burdens on the pilot and crew to keep the aircraft operating as desired.  The same sort of concept also applies to software. How much time and effort is required to keep a software system operating properly? Costs here can range from everything to periodic backups, monitoring, difficulty of installation and upgrades, environment management, complexity of testing... the list goes on and on. Viewed from that perspetive, you should ask yourself explicitly if you and your team are you building a system with a low or a high workload. What does it take to keep your system running, and is it appropriate for what it does?&lt;/p&gt;&lt;p&gt;At some level, this is old news. Build, deployment, and test automation have been on the software professional's agenda for literally decades at this point. 17 years ago, Jeff Atwood wrote about Rico Mariani's highly related concept of &lt;a href="https://blog.codinghorror.com/falling-into-the-pit-of-success/"&gt;the pit of success&lt;/a&gt;. That said, how close are you really to where you want to be with respect to the systems you help build and maintain? Even if the concept of workload is old, the need to revisit it and make sure you're handling it appropriately is evergreen.&lt;/p&gt;</description><author>Mike Schaeffer</author><pubDate>Fri, 22 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://mschaef.com/workload</guid></item><item><title>Mixtape Maker</title><link>https://lagomor.ph/projects/cassette/</link><description>&lt;p&gt;Tired of hand-calculating how you should split up your playlist between the two sides of a cassette tape? This widget is designed to make it simple; set your tape length, drag your music files directly onto this page, and move your tracks between the two playlists.&lt;/p&gt;
&lt;p&gt;When you&amp;rsquo;re ready to record, connect your computer to your tape deck (however you prefer to do so) and press play!&lt;/p&gt;
&lt;p&gt;The player will automatically play blank gaps between songs - but it doesn&amp;rsquo;t add gaps to the beginning or end, so be sure your tape is ready to record.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Thu, 21 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/projects/cassette/</guid></item><item><title>The simple life</title><link>https://martinrue.com/the-simple-life</link><description>More things should not be used than are necessary.</description><author>Martin Rue</author><pubDate>Thu, 21 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/the-simple-life</guid></item><item><title>The RAG Challenge: A GitHub Copilot Experiment</title><link>https://prashamhtrivedi.in/github-copilot-rag-app/</link><description>Recreating Tim Kitchen&amp;rsquo;s RAG application using GitHub Copilot and exploring how it compares to other AI coding assistants like Aider, Cursor, and Windsurf.</description><author>Prasham H Trivedi</author><pubDate>Thu, 21 Nov 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/github-copilot-rag-app/</guid></item><item><title>chatgpt-shell goes offline</title><link>https://xenodium.com/chatgpt-shell-goes-offline</link><description>&lt;p&gt;Since &lt;a href="https://lmno.lol/alvaro/chatgpt-shell-goes-multi-model"&gt;chatgpt-shell going multi-model&lt;/a&gt;, it was only a matter of time until we added support for local/offline models. As of version 2.0.6, &lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt; has a basic &lt;a href="https://ollama.com/"&gt;Ollama&lt;/a&gt; implementation (llama3.2 for now).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chatgpt-shell&lt;/code&gt; is more than a shell. Check out the &lt;a href="https://lmno.lol/alvaro/chatgpt-shell-goes-multi-model"&gt;demos in the previous post&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/chatgpt-shell-goes-offline/who-offline.gif" /&gt;&lt;/p&gt;
&lt;p&gt;For anyone keen on keeping all their LLM interactions offline, Ollama seems like a great option. I'm an Ollama noobie myself and already looking forward to getting acquainted. Having an offline LLM available at my Emacs fingertips will be super convenient.&lt;/p&gt;
&lt;p&gt;For the more familiar with Ollama, please give the &lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt; integration a try (it's on &lt;a href="https://melpa.org/#/chatgpt-shell"&gt;MELPA&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;v2.0.6 has a basic/inital Ollama implementation. Please give it a good run and &lt;a href="https://github.com/xenodium/chatgpt-shell/issues/new"&gt;report bugs&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You can swap models via:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;M-x chatgpt-shell-swap-model
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Help make this project sustainable. &lt;a href="https://github.com/sponsors/xenodium"&gt;Sponsor the work&lt;/a&gt;.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Thu, 21 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/chatgpt-shell-goes-offline</guid></item><item><title>The Curse of Markdown</title><link>https://codehike.org/blog/the-curse-of-markdown</link><description>And the content website wasteland</description><author>Rodrigo Pombo</author><pubDate>Thu, 21 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://codehike.org/blog/the-curse-of-markdown</guid></item><item><title>Software Is Bananas</title><link>https://prashamhtrivedi.in/software_is_bananas/</link><description>Kent Beck has shared some good insights in his substack. If I have to start with something, this is probably it. This post takes a short and stingy look around real &amp;ldquo;aglileness&amp;rdquo; of software development. Short but quick deployments and closer feedback loops instead of long and drawn out processes. Because at the end of the day, there is one bug lingering only in production enviorment which is waiting to be revealed.</description><author>Prasham H Trivedi</author><pubDate>Thu, 21 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/software_is_bananas/</guid></item><item><title>Should Programming Languages be Safe or Powerful?</title><link>https://lambdaland.org/posts/2024-11-21_powerful_or_safe_languages/</link><description>&lt;p&gt;Should a programming language be powerful and let a programmer do a lot, or should it be safe and protect the programmer from bad mistakes? Contrary to what the title insinuates, these are &lt;em&gt;not&lt;/em&gt; diametrically opposed attributes. Nevertheless, this is the mindset that underlies notions such as, &amp;ldquo;macros, manual memory management, etc. are power tools—they&amp;rsquo;re not supposed to be safe.&amp;rdquo; If safety and power are not necessarily opposed, why does this notion persist?&lt;/p&gt;
&lt;p&gt;The problem—I think—is that historically you &lt;em&gt;did&lt;/em&gt; have to trade safety for certain kinds of power: if you wanted to write a high-performance device driver, C—with all its unsafe behavior—was your only option. This founded the idea that the &amp;ldquo;power tools&amp;rdquo; of the industry were fundamentally dangerous.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a few things wrong with this though:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Power is relative to the domain of interest.&lt;/strong&gt; Both Haskell and C are powerful, but in completely different ways. So, when judging whether an aspect of a language is powerful or not, consider its application.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Expressive languages get you power without sacrificing safety.&lt;/strong&gt; New advances in programming language research have found ways to express problem domains more precisely. This means that we have less and less reason to breach safety and reach into the unsafe implementation details to get our work done.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;It&amp;rsquo;s good to add safety to power tools.&lt;/strong&gt; A safe power tool is more trustworthy than an unsafe one. This holds for real-world tools: I will never use a table saw without a functioning saw stop.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Specifically in the case of macros, there&amp;rsquo;s been an evolution from powerful-but-unsafe procedural macros in Lisp to safe-but-less-powerful pattern macros in Scheme, and finally to &lt;strong&gt;powerful-and-safe&lt;/strong&gt; macros in Racket.&lt;/p&gt;
&lt;p&gt;More safety means higher reliability—something that everyone wants. And with advances in making languages more expressive, you can have a language perfectly suited to a particular domain without sacrificing safety.&lt;/p&gt;
&lt;h2 id="what-makes-a-language-powerful"&gt;
  What makes a language powerful?
  &lt;a class="anchor" href="#what-makes-a-language-powerful"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;A language that lets you do more of what you want to do is more powerful than a language where you can&amp;rsquo;t do what you want. But what does &amp;ldquo;what you want to do&amp;rdquo; encompass? If you want to write device drivers, then C is great for you. However, C is not as expressive in some of the ways that, say, Haskell is. For example, in Haskell, I can write lazy, recursive definitions. Here&amp;rsquo;s a list of all&lt;label class="margin-toggle sidenote-number" for="sn1"&gt;&lt;/label&gt;
&lt;input class="margin-toggle" id="sn1" type="checkbox" /&gt;
&lt;span class="sidenote"&gt;
Yes, &lt;em&gt;all&lt;/em&gt; the Fibonacci numbers. Haskell is lazy; this will compute as many as you ask for.
&lt;/span&gt;
the Fibonacci numbers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-haskell"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;fibs&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;=&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;0&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;:&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;:&lt;/span&gt; zipWith &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; fibs &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;tail fibs&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Before you tell me that that&amp;rsquo;s just a useless cute trick, I actually had to use this when I was building the balancing algorithm in my rope data structure for &lt;a href="https://codeberg.org/ashton314/ysue"&gt;my text editor written in Haskell&lt;/a&gt;. Haskell is incredibly powerful in an &lt;em&gt;expressive&lt;/em&gt; sense: a single line of code can elegantly capture a complicated computation.&lt;/p&gt;
&lt;div class="epigraph"&gt;
&lt;blockquote&gt;
&lt;p&gt;The purpose of abstraction is not to be vague, but to create a new semantic level in which one can be absolutely precise.&lt;/p&gt;
&lt;footer&gt;
&lt;p&gt;Edsgar Dijkstra&lt;/p&gt;
&lt;/footer&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p&gt;Power is closely related to the domain of interest: a language is powerful in a particular realm of problems. C is powerful for working with memory directly. Conversely, Haskell or Racket is more powerful than C in pretty much every other domain because these languages give the user tremendous ability to &lt;em&gt;match the program to the domain&lt;/em&gt;. This is a meta-power that sets high-level languages apart from lower-level ones.&lt;/p&gt;
&lt;p&gt;Safe languages can be just as powerful as their unsafe counterparts—in many cases, they are &lt;em&gt;more&lt;/em&gt; powerful because the abstractions they create better fit the domain. Whenever a tradeoff between power and safety must be made, that is a sign that the language is not the right fit for the domain.&lt;/p&gt;
&lt;p&gt;Consider how immutability gives you &lt;em&gt;local reasoning power&lt;/em&gt;. At one of my industry jobs, our codebase was a mixture of Ruby and Elixir. Both are safe languages, but Elixir is immutable. When I was working on some Elixir code, I could read:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-elixir"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;user &lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt; get_user&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;session&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;name &lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt; get_user_name&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;user&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;do_something_else&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;user&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and I didn&amp;rsquo;t have to worry about &lt;code&gt;user&lt;/code&gt; getting modified in the call to &lt;code&gt;get_user_name&lt;/code&gt;. To understand the output of this function, I didn&amp;rsquo;t have to worry too much about the implementation of &lt;code&gt;get_user_name&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In contrast, if you did the same sort of thing in Ruby:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-ruby"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;user &lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt; get_user&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;session&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1;"&gt;name&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt; user&lt;span style="color: #81a1c1;"&gt;.&lt;/span&gt;get_name&lt;span style="color: #eceff4;"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;user&lt;span style="color: #81a1c1;"&gt;.&lt;/span&gt;do_something_else&lt;span style="color: #eceff4;"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;the &lt;code&gt;get_name&lt;/code&gt; method &lt;em&gt;could&lt;/em&gt; do something sneaky like set name to &lt;code&gt;&amp;quot;blank&amp;quot;&lt;/code&gt; if it didn&amp;rsquo;t exist.&lt;label class="margin-toggle sidenote-number" for="sn2"&gt;&lt;/label&gt;
&lt;input class="margin-toggle" id="sn2" type="checkbox" /&gt;
&lt;span class="sidenote"&gt;
You might think, &amp;ldquo;well, just document that behavior.&amp;rdquo; Now I need to read the documentation of &lt;em&gt;every&lt;/em&gt; function I encounter—I might as well go read the code to be sure the documentation isn&amp;rsquo;t out of date. Local reasoning means to understand what &lt;code&gt;do_something_else&lt;/code&gt; is passed, I don&amp;rsquo;t have to worry &lt;em&gt;in the first place&lt;/em&gt; if &lt;code&gt;get_name&lt;/code&gt; will do somethig to the result of &lt;code&gt;get_user&lt;/code&gt;.
&lt;/span&gt;
In this case, I &lt;em&gt;did&lt;/em&gt; have to understand what every method call did to understand the function. This made it harder to track down errors because I had to account for all the side effects that &lt;em&gt;could&lt;/em&gt; happen at every method call.&lt;/p&gt;
&lt;p&gt;Certain things like immutability might seem constraining, but &lt;a href="https://www.youtube.com/watch?v=GqmsQeSzMdw"&gt;constraints can liberate you&lt;/a&gt; by allowing you to rely on particular behaviors. Elixir doesn&amp;rsquo;t let you modify things in-place, but you can rely on this, which makes understanding and composing code easier. Haskell forces you to express side-effects in the type system, but this lets you know that calling a function with a signature like &lt;code&gt;String → Int&lt;/code&gt; won&amp;rsquo;t do any IO or throw an exception. Rust doesn&amp;rsquo;t have &lt;code&gt;null&lt;/code&gt; like in Java, but you know when you get a pointer, you can safely dereference it and you don&amp;rsquo;t have to do all the null checking that you have to do in Java.&lt;/p&gt;
&lt;h2 id="case-study-macros-in-lisp-scheme-and-racket"&gt;
  Case study: macros in Lisp, Scheme, and Racket
  &lt;a class="anchor" href="#case-study-macros-in-lisp-scheme-and-racket"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The evolution of syntax macros in Lisp, Scheme, and Racket provide an interesting real-world instance of how safety and power can start off as a trade-off, but with better language design, become complimentary.&lt;/p&gt;
&lt;h3 id="lisp-macros-unsafe-but-powerful"&gt;
  Lisp macros: unsafe but powerful
  &lt;a class="anchor" href="#lisp-macros-unsafe-but-powerful"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I don&amp;rsquo;t have the space here to do a deep dive into Lisp macros, but here&amp;rsquo;s the short of it: Lisp macros are just functions that receive code as data. This code is represented as nested lists of symbols. All a macro needs to do is return a &lt;em&gt;new&lt;/em&gt; list of symbols that will be spliced right into the call site.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;defmacro&lt;/span&gt; my-or &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;thing1 thing2&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #81a1c1;"&gt;`&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;tmp &lt;span style="color: #81a1c1;"&gt;,&lt;/span&gt;thing1&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; tmp tmp &lt;span style="color: #81a1c1;"&gt;,&lt;/span&gt;thing2&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; calling the macro&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;my-or &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;2&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; expands to&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;tmp &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; tmp tmp &lt;span style="color: #b48ead;"&gt;2&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;  &lt;span style="color: #616e87; font-style: italic;"&gt;;=&amp;gt; 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The problem with this is that these macros are &lt;em&gt;unhygienic&lt;/em&gt;: if I introduce a new variable, as I did with &lt;code&gt;tmp&lt;/code&gt; in &lt;code&gt;my-or&lt;/code&gt;, that is just a bare symbol that can be inadvertently captured producing unexpected output:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;tmp &lt;span style="color: #b48ead;"&gt;99&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;my-or &lt;span style="color: #8fbcbb;"&gt;nil&lt;/span&gt; tmp&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; expands to&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;tmp &lt;span style="color: #b48ead;"&gt;99&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;tmp &lt;span style="color: #8fbcbb;"&gt;nil&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; tmp tmp tmp&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;;=&amp;gt; nil&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is very bad! To use a macro safely, you need to be sure that it&amp;rsquo;s not introducing variables that you might accidentally capture. Lisp provides a mechanism&lt;label class="margin-toggle sidenote-number" for="sn3"&gt;&lt;/label&gt;
&lt;input class="margin-toggle" id="sn3" type="checkbox" /&gt;
&lt;span class="sidenote"&gt;
Lisp has a function called &lt;code&gt;gensym&lt;/code&gt; which makes a fresh symbol for you to use. Some other languages such as &lt;a href="https://docs.julialang.org/en/v1/base/base/#Base.gensym"&gt;Julia&lt;/a&gt; have a &lt;code&gt;gensym&lt;/code&gt; function; &lt;code&gt;gensym&lt;/code&gt; is a poor substitute for proper hygiene.
&lt;/span&gt;
to avoid some of the pitfalls with variable capture, but that&amp;rsquo;s not the end of the danger. If I have a macro that expands to a call to a function, e.g. &lt;code&gt;printf&lt;/code&gt;, I would expect this to be the &lt;code&gt;printf&lt;/code&gt; in scope at the time I defined the macro. However, this might not be the case—a user might inadvertently redefine a function, and then the macro would not behave in the expected way.&lt;/p&gt;
&lt;h3 id="scheme-macros-safe-but-less-powerful"&gt;
  Scheme macros: safe but less powerful
  &lt;a class="anchor" href="#scheme-macros-safe-but-less-powerful"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Scheme has a faculty called &lt;code&gt;syntax-rules&lt;/code&gt;, which lets you define transformations between a pattern and a template:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-scheme"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define-syntax &lt;/span&gt;my-or
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;syntax-rules &lt;/span&gt;&lt;span style="color: #eceff4;"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;_&lt;/span&gt; thing1 thing2&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let &lt;/span&gt;&lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;tmp&lt;/span&gt; thing1&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if &lt;/span&gt;tmp tmp thing2&lt;span style="color: #eceff4;"&gt;)))))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="marginnote"&gt;
&lt;p&gt;&lt;a href="https://doc.rust-lang.org/book/ch19-06-macros.html"&gt;Rust&amp;rsquo;s &lt;code&gt;macro_rules!&lt;/code&gt; form&lt;/a&gt; is essentially &lt;code&gt;syntax-rules&lt;/code&gt; from Scheme, but a little fancier with some syntax classes like &lt;code&gt;:expr&lt;/code&gt; and such.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This is safe; the examples from the Lisp run as expected:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-scheme"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;my-or&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;2&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;                      &lt;span style="color: #616e87; font-style: italic;"&gt;;=&amp;gt; 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;my-or&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;#f&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;42&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;                    &lt;span style="color: #616e87; font-style: italic;"&gt;;=&amp;gt; 42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let &lt;/span&gt;&lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;tmp&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;99&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;my-or&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;#f&lt;/span&gt; tmp&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;  &lt;span style="color: #616e87; font-style: italic;"&gt;;=&amp;gt; 99&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;However, we&amp;rsquo;ve lost some of the power because we can only define transformations between templates. We can&amp;rsquo;t, for example, write a macro that does some deep inspection of the code and makes decisions on how to expand. Furthermore, there&amp;rsquo;s no way for us to intentionally break hygiene when we really want to.&lt;/p&gt;
&lt;h3 id="racket-macros-the-best-of-both-worlds"&gt;
  Racket macros: the best of both worlds
  &lt;a class="anchor" href="#racket-macros-the-best-of-both-worlds"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Racket resolves the dilemma between having to choose between powerful Lisp-like procedural macros, and safe Scheme-like hygienic macros by giving us fully hygienic procedural macros! I have &lt;a href="https://lambdaland.org/posts/2023-10-17_fearless_macros/"&gt;another blog post discussing macros in Lisp, Scheme, and Racket&lt;/a&gt; and I go into some detail about the evolution of those macro systems.&lt;/p&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;And if you want to dive deep into macro hygiene, see Matthew Butterick&amp;rsquo;s excellent &lt;a href="https://beautifulracket.com/explainer/hygiene.html"&gt;explainer on Hygiene&lt;/a&gt; from his book &lt;a href="https://beautifulracket.com/"&gt;&lt;em&gt;Beautiful Racket&lt;/em&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The upshot of it is that Racket uses a combination of features (scope sets, syntax objects, etc.) to give the user a richer way of specifying syntax than simple dumb lists of symbols. This avoids inadvertent variable capture as well as keeps function references lined up nicely. However, macros can still do arbitrary computation, which means that we&amp;rsquo;re not constrained in the way that the pattern-transformation macros in Scheme are.&lt;/p&gt;
&lt;p&gt;And just to prove that Racket is just as powerful as Common Lisp, here&amp;rsquo;s the classic &lt;code&gt;aif&lt;/code&gt; macro:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;#lang &lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;racket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;require&lt;/span&gt; racket/stxparam syntax/parse/define&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;define-syntax-parameter it
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;lambda&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;stx&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;raise-syntax-error&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;syntax-e&lt;/span&gt; stx&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                        &lt;span style="color: #a3be8c;"&gt;"can only be used inside aif"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define-syntax&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;aif stx&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;syntax-parse stx
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;_&lt;/span&gt; test tcase fcase&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #81a1c1;"&gt;#'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;tmp test&lt;span style="color: #eceff4;"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; tmp
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;syntax-parameterize
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                 &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;it &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;make-rename-transformer&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;#'&lt;/span&gt;tmp&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;               tcase&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             fcase&lt;span style="color: #eceff4;"&gt;))]))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;aif &lt;span style="color: #b48ead;"&gt;41&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; it &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;whatever&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;;=&amp;gt; 42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;it                          &lt;span style="color: #616e87; font-style: italic;"&gt;;error: it: can only be used inside aif&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This example is inspired by Greg Hendershott&amp;rsquo;s fabulous tutorial &lt;a href="https://www.greghendershott.com/fear-of-macros/index.html"&gt;&lt;em&gt;Fear of Macros&lt;/em&gt;&lt;/a&gt;. The &lt;code&gt;define-syntax-parameter&lt;/code&gt; bit lets us introduce new bindings &lt;em&gt;intentionally&lt;/em&gt;, whilst still keeping us from accidental breaches of macro hygiene.&lt;/p&gt;
&lt;p&gt;Consequentially, &lt;strong&gt;Racket&amp;rsquo;s macro system is far more useful than Lisp or Scheme&amp;rsquo;s systems, and this because of Racket&amp;rsquo;s safety and expressiveness.&lt;/strong&gt; You can actually build trustworthy systems on top of Racket&amp;rsquo;s macro system because you&amp;rsquo;re not constantly foot-gunning yourself with hygiene malfunctions, and the macros are expressive enough to do some &lt;a href="https://lambdaland.org/posts/2023-08-14_types_with_macros/"&gt;rather complicated things&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="towards-greater-safety-and-reliability"&gt;
  Towards greater safety and reliability
  &lt;a class="anchor" href="#towards-greater-safety-and-reliability"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Safe systems let us build software that is more capable and more reliable. &lt;strong&gt;Unsafe power is something to improve, not grudgingly accept—and much less defend as somehow desirable.&lt;/strong&gt; Languages like Rust and Zig have made systems programming immune to whole hosts of errors by being more expressive than C, and languages like Racket are leading the way in making metaprogramming more useful reliable and less like dark magic.&lt;/p&gt;
&lt;h2 id="further-reading"&gt;
  Further reading
  &lt;a class="anchor" href="#further-reading"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;If you want to learn more about writing macros in Racket, check out &lt;a href="https://beautifulracket.com/"&gt;&lt;em&gt;Beautiful Racket&lt;/em&gt;&lt;/a&gt; by Matthew Butterick and &lt;a href="https://www.greghendershott.com/fear-of-macros/index.html"&gt;&lt;em&gt;Fear of Macros&lt;/em&gt;&lt;/a&gt; by Greg Hendershott.&lt;/p&gt;
&lt;p&gt;I highly recommend listening Runar Bjarnason&amp;rsquo;s talk at Scala World, &lt;a href="https://www.youtube.com/watch?v=GqmsQeSzMdw"&gt;&lt;em&gt;Constraints Liberate, Liberties Constrain&lt;/em&gt;&lt;/a&gt;, wherein he discusses how constraining one part of a system can open up freedoms of later components that build on that constrained part.&lt;/p&gt;</description><author>Ashton Wiersdorf on Lambda Land</author><pubDate>Thu, 21 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2024-11-21_powerful_or_safe_languages/</guid></item><item><title>Rebecca purple</title><link>https://ilearnt.com/blog/rebeccapurple/</link><description>&lt;p&gt;The community for the CSS-Next repository recently had a vote to decide on a new official logo. The colour chosen for the new logo was &lt;em&gt;rebeccapurple&lt;/em&gt;. This colour was introduced to the CSS specification in 2014.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Wed, 20 Nov 2024 22:48:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/rebeccapurple/</guid></item><item><title>Video Editor with FFmpeg Commands</title><link>https://newbeelearn.com/tools/videoeditor/</link><description>Easily edit videos, add logos, text and export customized FFmpeg commands for advanced video editing workflows.</description><author>Welcome to my weblog on Newbeelearn</author><pubDate>Wed, 20 Nov 2024 20:31:23 GMT</pubDate><guid isPermaLink="true">https://newbeelearn.com/tools/videoeditor/</guid></item><item><title>Your keystroke biometrics are everywhere</title><link>https://seirdy.one/notes/2024/11/20/your-keystroke-biometrics-are-everywhere/</link><description>&lt;p&gt;Real time collaboration software and text boxes that rapidly save drafts to the cloud essentially log your fingerprintable typing behavior. The industry refers to this information as “keystroke dynamics” or “typing biometrics”.&lt;/p&gt;&lt;p&gt;Other modern “operator signatures” are easier to minimize. A user can learn to &lt;a href="https://seirdy.one//posts/2022/07/09/stylometric-fingerprinting-redux/"&gt;obfuscate writing style&lt;/a&gt;, or can use keyboard navigation with different pointing devices to limit &lt;a href="http://jcarlosnorte.com/security/2016/03/06/advanced-tor-browser-fingerprinting.html"&gt;fingerprinting of mouse behavior&lt;/a&gt;. &lt;ins datetime="2024-11-20T15:56:31+00:00"&gt;Update: switched link to Kloak to Whonix’ updated fork.&lt;/ins&gt;&lt;/p&gt;&lt;p&gt;Keystroke biometrics are difficult to anonymize without &lt;a href="https://github.com/Whonix/kloak"&gt;installing software such as kloak&lt;/a&gt; or browser extensions (the latter of which may add fingerprintable vectors) designed to cloak some of your typing habits. Signature typos, approximate typing speed, etc. will still leak. Alternatively, we could normalize typing messages out in a simple offline editors that don’t store revision history before pasting them into other input fields.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Wed, 20 Nov 2024 17:56:31 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2024/11/20/your-keystroke-biometrics-are-everywhere/</guid></item><item><title>Why Bluesky feels right</title><link>https://goodinternet.substack.com/p/why-bluesky-feels-right</link><description>Bskyjuice Bskyjuice Bskyjuice.</description><author>GOOD INTERNET</author><pubDate>Wed, 20 Nov 2024 13:39:07 GMT</pubDate><guid isPermaLink="true">https://goodinternet.substack.com/p/why-bluesky-feels-right</guid></item><item><title>DETAILS part 3, a image series</title><link>https://heidenstedt.org/posts/2024/details-part-3-a-image-series/</link><description>&lt;p&gt;
      &lt;em&gt;Best viewed on the &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/"&gt;original page&lt;/a&gt;, where extended functionality like the
    footnote helper is available.&lt;/em&gt;
    &lt;/p&gt;&lt;p&gt;This is a street photography series captured during my travels and excursions. I am considering selling prints of these images, but none currently exist. If you would like a print version of my work, please contact me and we can discuss the details.&lt;/p&gt;
&lt;p&gt;Street photography featuring scenes across Italy.&lt;/p&gt;


Total images: 31



  
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/1.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/1.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/1_hu30b53d4004a70f79641ed3b5f864e087_7870864_300x0_resize_q85_h2_lanczos.webp" width="4160" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF3874.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF3874.jpg" height="4008" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF3874_hua07b970ee5153b61a0d983a2f144ac90_5783458_300x0_resize_q85_h2_lanczos.webp" width="3692" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF3880-HDR.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF3880-HDR.jpg" height="4155" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF3880-HDR_hua2cfb19137989ba2776d0471c7708ccc_12863504_300x0_resize_q85_h2_lanczos.webp" width="6233" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4047.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4047.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4047_hua9729eb2b0d6abb7653af398ff36ef05_10820268_300x0_resize_q85_h2_lanczos.webp" width="6240" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4090.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
        
            
            
        
        
        

        &lt;img alt="img/DSCF4090.jpg" height="1781" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4090_hu15285702d24030222fcd73da67cdf83d_1742413_300x0_resize_q85_h2_lanczos.webp" width="1781" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4110.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4110.jpg" height="2933" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4110_hu5bd292024e4d9a3701ace3af971ae85d_4251113_300x0_resize_q85_h2_lanczos.webp" width="2933" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4134.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4134.jpg" height="2520" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4134_hua7584a3ff5275b9501757ee67b350376_3311900_300x0_resize_q85_h2_lanczos.webp" width="2520" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4234.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4234.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4234_hua77b9d833bb559266e4a509ec06d30e5_11120684_300x0_resize_q85_h2_lanczos.webp" width="6240" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4266.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4266.jpg" height="6240" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4266_hu47cf0e6994b9a440953e93e252554f3c_12402277_300x0_resize_q85_h2_lanczos.webp" width="4160" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4269.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4269.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4269_hu38b66b17b232e86f3f1bbe2d99e3f788_11963240_300x0_resize_q85_h2_lanczos.webp" width="6240" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4272.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4272.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4272_hu8cd493828690a0c2ec5558079573f213_12248858_300x0_resize_q85_h2_lanczos.webp" width="6240" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4275.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4275.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4275_hu15b91d1beecd680bc4cd5bb1df644f7c_11794918_300x0_resize_q85_h2_lanczos.webp" width="6240" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4284.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4284.jpg" height="3931" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4284_hu99e1d6d6b9a350ef6088eeafd3b1cadf_6622911_300x0_resize_q85_h2_lanczos.webp" width="5896" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4323-2.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4323-2.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4323-2_hu023728e0a58bc92fd50fc0dbd66f1464_13284962_300x0_resize_q85_h2_lanczos.webp" width="6240" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4323.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4323.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4323_hu62d5ff4adb62de93f47e0efb174ee2d9_9125713_300x0_resize_q85_h2_lanczos.webp" width="4160" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4335-HDR.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4335-HDR.jpg" height="3340" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4335-HDR_hu0744915525c051a54038afa0fa12807d_5466650_300x0_resize_q85_h2_lanczos.webp" width="3340" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4399.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4399.jpg" height="3473" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4399_hued1a8bcbcc6aef6f464a58d51a5e02e7_5615707_300x0_resize_q85_h2_lanczos.webp" width="3473" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4419.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4419.jpg" height="3626" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4419_hu9074b51090e3355b26c652426f983e5b_5486555_300x0_resize_q85_h2_lanczos.webp" width="3626" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4461.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4461.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4461_hub7459e3fb70a8166f40595bf41cc993c_8265589_300x0_resize_q85_h2_lanczos.webp" width="4160" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4473.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4473.jpg" height="4156" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4473_hu3f007a2168994234f363c2de6fe25689_8356441_300x0_resize_q85_h2_lanczos.webp" width="4156" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4506.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4506.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4506_hu9a6a0be6d0f8509703bc7bf42ed8461e_8541629_300x0_resize_q85_h2_lanczos.webp" width="4160" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4542.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4542.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4542_huf82c8f45d60ff47c05cda689b19da726_9322837_300x0_resize_q85_h2_lanczos.webp" width="4160" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4602.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4602.jpg" height="3902" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4602_huf6bae3648559481f9ea9f42bf8e00749_10177591_300x0_resize_q85_h2_lanczos.webp" width="5853" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4630.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4630.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4630_hu4cabcebf7f7252797c13fea19f1421aa_7193630_300x0_resize_q85_h2_lanczos.webp" width="4160" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4637.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4637.jpg" height="3296" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4637_hue2e1792fe177b64f495a1f716aa9d2b7_5302420_300x0_resize_q85_h2_lanczos.webp" width="3296" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4647.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4647.jpg" height="3941" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4647_hu6d121eeb0ee0ce1a6d6a72071259b472_9477336_300x0_resize_q85_h2_lanczos.webp" width="3941" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4653.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4653.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4653_hu291e75db6266e54d1c24b0178ed72745_8759060_300x0_resize_q85_h2_lanczos.webp" width="4160" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4655.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4655.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4655_hu21e226916348cd1c32b8a93a141c92ee_8208100_300x0_resize_q85_h2_lanczos.webp" width="4160" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4696-HDR.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4696-HDR.jpg" height="4131" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4696-HDR_hu5c0387dd733896e0405d3ddeb6c93af0_7206521_300x0_resize_q85_h2_lanczos.webp" width="4131" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4793.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4793.jpg" height="3241" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4793_hua55c116cf4ac3a7d213c65b359e26d37_4614144_300x0_resize_q85_h2_lanczos.webp" width="3241" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;
  
    &lt;div class="imageLoadingWrap noMaxHeight gallery"&gt;
      &lt;a href="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4797.jpg"&gt;
        
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
            
            
            
            
            
        
        
        

        &lt;img alt="img/DSCF4797.jpg" height="4160" src="https://heidenstedt.org/posts/2024/details-part-3-a-image-series/img/DSCF4797_hu559f5a344b79695414dfd2096ef885ea_7818863_300x0_resize_q85_h2_lanczos.webp" width="4160" /&gt;
        &lt;div class="imageLoading"&gt;&lt;/div&gt;
      &lt;/a&gt;
    &lt;/div&gt;</description><author>Mia Heidenstedt</author><pubDate>Wed, 20 Nov 2024 11:17:10 GMT</pubDate><guid isPermaLink="true">https://heidenstedt.org/posts/2024/details-part-3-a-image-series/</guid></item><item><title>You can use almost anything as a key file for your encrypted storage device</title><link>https://ounapuu.ee/posts/2024/11/20/keyfiles/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/11/20/keyfiles/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;Imagine that you have an unencrypted drive containing your private data and one day it starts throwing a bunch of
errors. You have backups of the data so you&amp;rsquo;ve got that part covered, but would you feel comfortable sending the drive
in to be warrantied? You have no control over who has access to that drive, and due to the drive failing you can&amp;rsquo;t
format it as well.&lt;/p&gt;
&lt;p&gt;Do you take the financial hit and buy a new drive, or send it in regardless and risk
someone looking through your files?&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve bought and sold a bunch of hard drives and SSD-s over the years&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;, and warrantied a few of them. I encrypt the
disk
on my personal and work machines because of the obvious security benefits, but for a long time I avoided doing the same
for other storage devices.&lt;/p&gt;
&lt;p&gt;Then I realized that I can just encrypt them and use almost anything as the key. All you need is a reasonably sized file
to pass to &lt;code&gt;cryptsetup&lt;/code&gt; as a key-file, refer to that key-file in &lt;code&gt;/etc/crypttab&lt;/code&gt;, and you&amp;rsquo;re good to
go!&lt;/p&gt;
&lt;p&gt;Be creative! The key file can be anything:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a dank meme&lt;/li&gt;
&lt;li&gt;a very short low resolution cat video&lt;/li&gt;
&lt;li&gt;an adorable photo of your dog&lt;/li&gt;
&lt;li&gt;a precious family photo&lt;/li&gt;
&lt;li&gt;Twilight fan fiction that you wrote back in 2011&lt;/li&gt;
&lt;li&gt;your letter of resignation at your last job&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Just mind the size, &lt;code&gt;cryptsetup&lt;/code&gt; wasn&amp;rsquo;t very happy with a 17MB full resolution image. A JPEG that&amp;rsquo;s less than a megabyte
in size worked well enough for me.&lt;/p&gt;
&lt;p&gt;On Linux, you can encrypt a partition with a cat picture using a command like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cryptsetup luksFormat /dev/sdx1 --key-file /path/to/cat.jpg
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You can make sure that the drive gets automatically decrypted at boot by defining it in &lt;code&gt;/etc/crypttab&lt;/code&gt;.
Find the UUID of the encrypted partition by running &lt;code&gt;ls -lah /dev/disk/by-uuid&lt;/code&gt; and see which one matches with
&lt;code&gt;/dev/sdx1&lt;/code&gt;.
If the UUID is &lt;code&gt;ef277bc2-d953-44c4-88af-8320aca76969&lt;/code&gt;, then a line in &lt;code&gt;/etc/crypttab&lt;/code&gt; would look like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;encryptedcatdrive UUID=ef277bc2-d953-44c4-88af-8320aca76969 /path/to/cat.jpg 
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Once unlocked, your decrypted partition will be available as &lt;code&gt;/dev/mapper/encryptedcatdrive&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Just make sure that the key file isn&amp;rsquo;t placed on the encrypted drive itself, otherwise you&amp;rsquo;ll lock yourself out for
good.&lt;/p&gt;
&lt;p&gt;For more information on LUKS disk encryption and its
capabilities, &lt;a href="https://wiki.archlinux.org/title/Dm-crypt/Device_encryption"&gt;see this handy Arch Wiki page.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This approach comes with some great benefits and a few downsides.&lt;/p&gt;
&lt;p&gt;On the bright side, you don&amp;rsquo;t have to worry about a stranger getting their hands on your data when selling a storage
device
or sending it in to be warrantied, assuming that you haven&amp;rsquo;t posted the key file on social media or anywhere else.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;
Just do a quick format of the drive, or format a very specific part to clear the LUKS headers, and the contents of the
drive are gone!&lt;/p&gt;
&lt;p&gt;If you can&amp;rsquo;t format the drive due to hardware issues, then you should still be safe, the attacker would
have to first &lt;em&gt;fix&lt;/em&gt; the drive (which isn&amp;rsquo;t a guaranteed success) and &lt;em&gt;then&lt;/em&gt; figure out the key, which will require a lot
of effort and time.&lt;/p&gt;
&lt;p&gt;The one obvious downside is that if you lose the key file, you lose the data. You can mitigate this by either adding
multiple key files as a backup option, or setting &lt;a href="https://xkcd.com/936/"&gt;a strong password&lt;/a&gt; that you can use in case
you lose your original key file.&lt;/p&gt;
&lt;p&gt;If the drive contains something of value to your family members, then it may be a good idea to specify those details in
a will, or by taping the backup password with instructions on the drive itself. If you end up selling the drive before
your personal expiration date arrives, then you can simply remove the instructions. Just make sure that your family
members or relatives know to plug in the drive to a Linux machine.&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I also like the thrill of hiding a key in plain sight. Did I use the cover photo of this post to encrypt my drives?
You&amp;rsquo;ll never know, and that&amp;rsquo;s what makes it fun!&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re paranoid or targeted by state actors, then you probably shouldn&amp;rsquo;t follow this advice, except the part where I
encourage encrypting stuff.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Hello, my name is Herman, and I&amp;rsquo;m a recovering data hoarder.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;if we use a simple .jpg as an example key file, then it&amp;rsquo;s highly likely that this won&amp;rsquo;t be an issue as most
messaging and social media platforms compress the hell out of the image. The compression is also lossy, meaning that the
process is irreversible and the end result is a completely different file from the perspective of the encrypted disk.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;if you&amp;rsquo;ve ever plugged in a drive with a Linux filesystem on a Windows machine, then you&amp;rsquo;ll know that it will
happily recommend formatting it. Oh, goodie.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>./techtipsy</author><pubDate>Wed, 20 Nov 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/11/20/keyfiles/</guid></item><item><title>Engineering AI Agents - OpenAI DevDay Singapore talk</title><link>https://www.swyx.io/ai-eng-agents</link><description>&lt;p&gt;this is the tracking doc for my talk on "Engineering AI Agents" for OpenAI DevDay Singapore. I'll add photos and notes and stuff when i'm done. note that the full slides contain a lot more info that i had to cut out of the ~~10~~ 9 minute talk.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Wed, 20 Nov 2024 04:06:58 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/ai-eng-agents</guid></item><item><title>chatgpt-shell goes multi-model</title><link>https://xenodium.com/chatgpt-shell-goes-multi-model</link><description>&lt;p&gt;Over the last few months, I've been chipping at implementing &lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt;'s most requested and biggest feature: multi-model support. Today, I get to unveil the first two implementations: &lt;a href="https://www.anthropic.com/claude"&gt;Anthropic's Claude&lt;/a&gt; and &lt;a href="https://gemini.google.com/"&gt;Google's Gemini&lt;/a&gt;.&lt;/p&gt;
&lt;img src="https://raw.githubusercontent.com/xenodium/chatgpt-shell/main/demos/multi-model-shell.gif" width="99%" /&gt;
&lt;h2&gt;Changing course&lt;/h2&gt;
&lt;p&gt;In the past, I envisioned a different path for multi-model support. By isolating shell logic into a new package (&lt;a href="https://github.com/xenodium/shell-maker"&gt;shell-maker&lt;/a&gt;), folks could use it as a &lt;a href="https://lmno.lol/alvaro/a-shell-maker"&gt;building block to create new shells&lt;/a&gt; (adding support for their favourite LLM).&lt;/p&gt;
&lt;p&gt;While each shell-maker-based shell currently shares a basic common experience, I did not foresee the minor differences affecting the general Emacs user experience. Learning the quirks of each new shell felt like unnecessary friction in developing muscle memory. I also became dependent on &lt;code&gt;chatgpt-shell&lt;/code&gt; features, which I often missed when using other shells.&lt;/p&gt;
&lt;p&gt;Along with slightly different shell experiences, we currently require multiple package installations (and setups). Depending on which camp you're on (batteries included vs fine-grained control) this may or may not be a downside.&lt;/p&gt;
&lt;p&gt;With every new &lt;code&gt;chatgpt-shell&lt;/code&gt; feature I showcased, I was often asked if they could be used with other LLM providers. I typically answered with &amp;quot;I've been meaning to work on this…&amp;quot; or &amp;quot;I heard you can do multi-model &lt;code&gt;chatgpt-shell&lt;/code&gt; using a bridge like &lt;a href="https://docs.litellm.ai/docs/"&gt;liteLLM&lt;/a&gt;&amp;quot;. Neither of these where great answers, resulting in me just postponing the chunky work.&lt;/p&gt;
&lt;p&gt;Eventually, I bit the bullet, changed course, and got to work on multi-model support. With my initial plan to spin multiple shells via &lt;code&gt;shell-maker&lt;/code&gt;, &lt;code&gt;chatgpt-shell&lt;/code&gt;'s implementation didn't exactly lend itself to support multiple clients. Long story short, &lt;code&gt;chatgpt-shell&lt;/code&gt; multi-model support required quite a bit of work. This where I divert to ask you to &lt;a href="https://github.com/sponsors/xenodium"&gt;help make this project sustainable by sponsoring the work&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Make this project sustainable&lt;/h2&gt;
&lt;p&gt;Maintaining, experimenting, implementing feature requests, and supporting open-source packages takes work. Today, chatgpt-shell has over &lt;a href="https://melpa.org/#/chatgpt-shell"&gt;20.5K downloads on MELPA&lt;/a&gt; and many untracked others elsewhere. If you're one of the happy users, &lt;a href="https://github.com/sponsors/xenodium"&gt;consider sponsoring the project&lt;/a&gt;. If you see potential, help &lt;a href="https://github.com/sponsors/xenodium"&gt;fuel development by sponsoring&lt;/a&gt; too.&lt;/p&gt;
&lt;p&gt;Perhaps you enjoy some of the content I write about? Find my Emacs posts/tips useful?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://xenodium.com/"&gt;Blog (xenodium.com)&lt;/a&gt; (Web)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lmno.lol/alvaro"&gt;Blog (lmno.lol/alvaro)&lt;/a&gt; (Web)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Alternatively, you want a blogging platform that skips the yucky side effects of the modern web?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I'm building &lt;a href="https://lmno.lol"&gt;lmno.lol&lt;/a&gt; (my blog is &lt;a href="https://lmno.lol/alvaro"&gt;there&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maybe you enjoy one of my other projects?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://plainorg.com"&gt;Plain Org&lt;/a&gt; (org mode / iOS)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://flathabits.com"&gt;Flat Habits&lt;/a&gt; (org mode / iOS)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/scratch/id1671420139"&gt;Scratch&lt;/a&gt; (org mode / iOS)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/macosrec"&gt;macosrec&lt;/a&gt; (macOS)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://apps.apple.com/us/app/fresh-eyes/id6480411697?mt=12"&gt;Fresh Eyes&lt;/a&gt; (macOS)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/dwim-shell-command"&gt;dwim-shell-command&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/company-org-block"&gt;company-org-block&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/org-block-capf"&gt;org-block-capf&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/ob-swiftui"&gt;ob-swiftui&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/ready-player"&gt;ready-player&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/sqlite-mode-extras"&gt;sqlite-mode-extras&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/ob-chatgpt-shell"&gt;ob-chatgpt-shell&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/dall-e-shell"&gt;dall-e-shell&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/ob-dall-e-shell"&gt;ob-dall-e-shell&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/shell-maker"&gt;shell-maker&lt;/a&gt; (Emacs)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So, umm… I'll just leave my GitHub sponsor page &lt;a href="https://github.com/sponsors/xenodium"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;chatgpt-shell, more than a shell&lt;/h2&gt;
&lt;p&gt;With chatgpt-shell being a &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Shell-Prompts.html"&gt;comint&lt;/a&gt; shell, you can bring your favourite Emacs flows along.&lt;/p&gt;
&lt;img src="https://raw.githubusercontent.com/xenodium/chatgpt-shell/main/demos/cyberpunk.gif" width="99%" /&gt;
&lt;p&gt;As I used &lt;code&gt;chatgpt-shell&lt;/code&gt; myself, I kept experimenting with different integrations and improvements. Read on for some of my favourites…&lt;/p&gt;
&lt;h3&gt;A shell hybrid&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;chatgpt-shell&lt;/code&gt; includes a compose buffer experience. This is my favourite and most frequently used mechanism to interact with LLMs.&lt;/p&gt;
&lt;p&gt;For example, select a region and invoke &lt;code&gt;M-x chatgpt-shell-prompt-compose&lt;/code&gt; (&lt;code&gt;C-c C-e&lt;/code&gt; is my preferred binding), and an editable buffer automatically copies the region and enables crafting a more thorough query. When ready, submit with the familiar &lt;code&gt;C-c C-c&lt;/code&gt; binding. The buffer automatically becomes read-only and enables single-character bindings.&lt;/p&gt;
&lt;img src="https://raw.githubusercontent.com/xenodium/chatgpt-shell/main/demos/compose.gif" width="99%" /&gt;
&lt;h3&gt;Navigation: n/p (or TAB/shift-TAB)&lt;/h3&gt;
&lt;p&gt;Navigate through source blocks (including previous submissions in history). Source blocks are automatically selected.&lt;/p&gt;
&lt;h3&gt;Reply: r&lt;/h3&gt;
&lt;p&gt;Reply with with follow-up requests using the &lt;code&gt;r&lt;/code&gt; binding.&lt;/p&gt;
&lt;h3&gt;Give me more: m&lt;/h3&gt;
&lt;p&gt;Want to ask for more of the same data? Press &lt;code&gt;m&lt;/code&gt; to request more of it. This is handy to follow up on any kind of list (suggestion, candidates, results, etc).&lt;/p&gt;
&lt;h3&gt;Request entire snippets: e&lt;/h3&gt;
&lt;p&gt;LLM being lazy and returning partial code? Press &lt;code&gt;e&lt;/code&gt; to request entire snippet.&lt;/p&gt;
&lt;h3&gt;Quick quick: q&lt;/h3&gt;
&lt;p&gt;I'm a big fan of quickly disposing of Emacs buffers with the &lt;code&gt;q&lt;/code&gt; binding. chatgpt-shell compose buffers are no exception.&lt;/p&gt;
&lt;h2&gt;Confirm inline mods (via diffs)&lt;/h2&gt;
&lt;p&gt;Request inline modifications, with explicit confirmation before accepting.&lt;/p&gt;
&lt;img src="https://raw.githubusercontent.com/xenodium/chatgpt-shell/main/demos/quick-insert.gif" width="99%" /&gt;
&lt;h2&gt;Execute snippets (a la &lt;a href="https://orgmode.org/worg/org-contrib/babel/intro.html"&gt;org babel&lt;/a&gt;)&lt;/h2&gt;
&lt;p&gt;Both the shell and the compose buffers enable users to execute source blocks via &lt;code&gt;C-c C-c&lt;/code&gt;, leveraging &lt;a href="https://orgmode.org/worg/org-contrib/babel/intro.html"&gt;org babel&lt;/a&gt;.&lt;/p&gt;
&lt;img src="https://raw.githubusercontent.com/xenodium/chatgpt-shell/main/demos/swiftui.gif" width="99%" /&gt;
&lt;h2&gt;Vision experiments&lt;/h2&gt;
&lt;p&gt;I've been experimenting with image queries (currently ChatGPT only, please &lt;a href="https://github.com/sponsors/xenodium"&gt;sponsor&lt;/a&gt; to help bring support for others).&lt;/p&gt;
&lt;p&gt;Below is a handy integration to extract Japanese vocabulary. There's also a generic image descriptor available via &lt;code&gt;M-x chatgpt-shell-describe-image&lt;/code&gt; that works on any Emacs image (via dired, image buffer, point on image, or selecting a desktop region).&lt;/p&gt;
&lt;img src="https://raw.githubusercontent.com/xenodium/chatgpt-shell/main/demos/japanese-weekdays.gif" width="99%" /&gt;
&lt;h2&gt;Supporting new models&lt;/h2&gt;
&lt;p&gt;Your favourite model not yet supported? File a &lt;a href="https://github.com/xenodium/chatgpt-shell/issues/new"&gt;feature request&lt;/a&gt;. You also know how to &lt;a href="https://github.com/sponsors/xenodium"&gt;fuel the project&lt;/a&gt;. Want to contribute new models? &lt;a href="https://github.com/xenodium/chatgpt-shell/issues/new"&gt;Reach out&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Local models&lt;/h2&gt;
&lt;p&gt;While the two new implementations rely on cloud APIs, local services are now possible. I've yet to use a local LLM, but please &lt;a href="https://github.com/xenodium/chatgpt-shell/issues/new"&gt;reach out&lt;/a&gt;, so we can make these happen too. Want to contribute?&lt;/p&gt;
&lt;h2&gt;Should chatgpt-shell rename?&lt;/h2&gt;
&lt;p&gt;With &lt;code&gt;chatgpt-shell&lt;/code&gt; going multi-model, it's not unreasonable to ask if this package should be renamed. Maybe it should. But that's additional work we can likely postpone for the time being (and avoid pushing users to migrate). For now, I'd prefer focusing on polishing the multi-model experience and work on ironing out any issues. For that, I'll need your help.&lt;/p&gt;
&lt;h2&gt;Take Gemini and Claude for a spin&lt;/h2&gt;
&lt;p&gt;Multi-model support required chunky structural changes. While I've been using it myself, I'll need wider usage to uncover issues. Please take it for a spin and &lt;a href="https://github.com/xenodium/chatgpt-shell/issues/new"&gt;file bugs or give feedback&lt;/a&gt;. Or if you just want to ping me, I'd love to hear about your experience (&lt;a href="https://indieweb.social/@xenodium"&gt;Mastodon&lt;/a&gt; / &lt;a href="https://twitter.com/xenodium"&gt;Twitter&lt;/a&gt; / &lt;a href="https://www.reddit.com/user/xenodium"&gt;Reddit&lt;/a&gt; / &lt;a href="mailto:me__AT__xenodium.com"&gt;Email&lt;/a&gt;).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Be sure to update to &lt;code&gt;chatgpt-shell&lt;/code&gt; v2.0.1 and &lt;code&gt;shell-maker&lt;/code&gt; v0.68.1 as a minimum.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;chatgpt-shell-anthropic-key&lt;/code&gt; or &lt;code&gt;chatgpt-shell-google-key&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Swap models with existing &lt;code&gt;M-x chatgpt-shell-swap-model-version&lt;/code&gt; or set a default with &lt;code&gt;(setq chatgpt-shell-model-version &amp;quot;claude-3-5-sonnet-20240620&amp;quot;)&lt;/code&gt; or &lt;code&gt;(setq chatgpt-shell-model-version &amp;quot;claude-gemini-1.5-pro-latest&amp;quot;)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Everything else should just work 🤞😅&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy Emacsing!&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Wed, 20 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/chatgpt-shell-goes-multi-model</guid></item><item><title>2024-11-20/01</title><link>https://ho.dges.online/pictures/2024-11-20-01/</link><description/><author>ho.dges.online</author><pubDate>Wed, 20 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-11-20-01/</guid></item><item><title>2024-11-20/02</title><link>https://ho.dges.online/pictures/2024-11-20-02/</link><description/><author>ho.dges.online</author><pubDate>Wed, 20 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-11-20-02/</guid></item><item><title>Bloom Filters and SQLite</title><link>https://boyter.org/posts/bloom-filters-sqlite/</link><description>&lt;p&gt;I was talking to someone recently about how searchcode.com works with regards to its bloom filter. The question came up about why it stores the index in memory and not on disk considering how fast disks are these days.&lt;/p&gt;
&lt;p&gt;Now the answer is firstly performance, but the second is that writing all the code to store the index on disk is a lot of work and I didn&amp;rsquo;t feel like working on it. However I was curious&amp;hellip; since I have been using SQLite a lot recently, how about storing the bloom filter on disk using that? It comes with its own indexes for stepping though the filters and is likely out of the box to be better than anything I could come up with quickly.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Wed, 20 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/bloom-filters-sqlite/</guid></item><item><title>Why The Singularity Is Impossible — Or Rather, Why It's Just a Bad Word In General</title><link>https://chrisfrew.in/blog/why-the-singularity-is-impossible/</link><description>Too many people not reading enough books and not understanding what they are arguing about.</description><author>Chris' Full Stack Blog RSS Feed</author><pubDate>Wed, 20 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://chrisfrew.in/blog/why-the-singularity-is-impossible/</guid></item><item><title>Bill 212</title><link>http://pxtl.ca/2024/11/20/bill-212/</link><description>&lt;p&gt;So in a fit of pettiness, the Government of Ontario is set to wrap up bike-lanes
in a 50-foot-ball of red tape to prevent their construction.  I asked on Bluesky
what was the best way to complain - if there was a petition I should sign or
something.  Craig Burley recommended I send a &lt;em&gt;fax&lt;/em&gt; of all things.  It's got a
good chance of being physical media in their space, and won't just be in a
slushpile of emails they ignore, or forgettable names on a petition.  So I wrote
up the letters for the Premier and the Minister of Transportation at lunch on
Tuesday and faxed them from my local UPS store.  Also emailed them for good measure.&lt;/p&gt;

&lt;p&gt;They're not my best writing but I was kind of in a rush.&lt;/p&gt;

&lt;p&gt;The bodies of the letters are attached below.&lt;/p&gt;</description><author>Pxtl.ca</author><pubDate>Wed, 20 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://pxtl.ca/2024/11/20/bill-212/</guid></item><item><title>Power to the power users</title><link>https://kinduff.com/2024/11/19/power-to-the-power-users/</link><description>How the creative uses of data structures by power users are transforming and inspiring new features in my product</description><author>Alejandro AR (kinduff)</author><pubDate>Tue, 19 Nov 2024 20:38:00 GMT</pubDate><guid isPermaLink="true">https://kinduff.com/2024/11/19/power-to-the-power-users/</guid></item><item><title>How to use Common Lisp on NixOS with C libraries</title><link>https://honza.pokorny.ca/2024/11/how-to-use-common-lisp-on-nixos-with-c-libraries/</link><description>&lt;p&gt;NixOS is great.  Well, until you open up a Common Lisp project, start a REPL, and well&amp;hellip; you get an error about missing headers to some C library.  We can make this work with a few steps.&lt;/p&gt;
&lt;p&gt;NixOS doesn&amp;rsquo;t really let you install libraries globally and you have to install these dependencies manually for every project.  Let&amp;rsquo;s do that now and see how we can make our REPL find them.&lt;/p&gt;
&lt;p&gt;In the root of your project, create a &lt;code&gt;shell.nix&lt;/code&gt; file with the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nix"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;{&lt;/span&gt; pkgs &lt;span style="color: #81a1c1;"&gt;?&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;import&lt;/span&gt; &lt;span style="color: #ebcb8b;"&gt;&amp;lt;nixpkgs&amp;gt;&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;{}&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;}:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;with&lt;/span&gt; pkgs&lt;span style="color: #eceff4;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;mkShell &lt;span style="color: #eceff4;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  buildInputs &lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    pkg-config
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    openssl
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then, enter into that shell with &lt;code&gt;nix-shell&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Start a slynk REPL in the nix shell.  You can use a script like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-common-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;push&lt;/span&gt; #p"~/.emacs.d/.local/straight/repos/sly/slynk/" asdf:*central-registry*&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;asdf:load-system &lt;span style="color: #a3be8c;"&gt;:slynk&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;slynk:create-server &lt;span style="color: #a3be8c;"&gt;:port&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;4008&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run it with &lt;code&gt;sbcl --load &amp;lt;script&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once the REPL is running, you can connect to it from Emacs with &lt;code&gt;M-x sly-connect&lt;/code&gt;.  That&amp;rsquo;s it.&lt;/p&gt;</description><author>Honza Pokorný</author><pubDate>Tue, 19 Nov 2024 20:00:00 GMT</pubDate><guid isPermaLink="true">https://honza.pokorny.ca/2024/11/how-to-use-common-lisp-on-nixos-with-c-libraries/</guid></item><item><title>Power Laws &amp;amp; Why Our New Album Won’t Make Any Money</title><link>https://www.quantable.com/analytics/power-laws-why-our-new-album-wont-make-any-money/</link><description>&lt;p&gt;That&amp;#8217;s the way you do it You play the guitar on the MTV That ain&amp;#8217;t workin&amp;#8217;, that&amp;#8217;s the way you do it Money for nothing and your chicks for free So said Dire Straits in 1985. When I was a kid, I legitimately thought that the lyrics were &amp;#8220;money for nothing and your checks for free&amp;#8221;. [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://www.quantable.com/analytics/power-laws-why-our-new-album-wont-make-any-money/"&gt;Power Laws &amp;#038; Why Our New Album Won&amp;#8217;t Make Any Money&lt;/a&gt; appeared first on &lt;a href="https://www.quantable.com"&gt;Quantable Analytics&lt;/a&gt;.&lt;/p&gt;</description><author>Quantable Analytics</author><pubDate>Tue, 19 Nov 2024 18:32:58 GMT</pubDate><guid isPermaLink="true">https://www.quantable.com/analytics/power-laws-why-our-new-album-wont-make-any-money/</guid></item><item><title>Connecting to eduroam at Oxford University</title><link>https://www.extua.pw/blog/2024/11/20/connecting_to_eduroam_at_oxford_university/</link><description>I’ve been setting myself up at Oxford University, going through the rotation of new accounts and passwords. So, I have to connect to eduroam with a new set of credentials.</description><author>extua</author><pubDate>Tue, 19 Nov 2024 18:00:00 GMT</pubDate><guid isPermaLink="true">https://www.extua.pw/blog/2024/11/20/connecting_to_eduroam_at_oxford_university/</guid></item><item><title>Communication Tip: The Power of Named Chats</title><link>https://seankilleen.com/2024/11/communication-tip-the-power-of-named-chats/</link><description>In many organizations, natural communication pathways start to form. Something happens or information needs to be sent out or a decision needs to be made, and you fire up a Teams window or Slack group message and invite the usual suspects.</description><author>SeanKilleen.com</author><pubDate>Tue, 19 Nov 2024 16:00:00 GMT</pubDate><guid isPermaLink="true">https://seankilleen.com/2024/11/communication-tip-the-power-of-named-chats/</guid></item><item><title>HexUploader mirror</title><link>https://www.krekr.nl/content/hexuploader-mirror/</link><description>Since the creator&amp;#8217;s site is unavailable and has been for some time, I&amp;#8217;m hosting a mirror of a binary of the handy HexUploader. It was available through a third party. This is a small Mac app that allows uploading .hex &amp;#8230; &lt;a href="https://www.krekr.nl/content/hexuploader-mirror/"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Gregor van Egdom</author><pubDate>Tue, 19 Nov 2024 10:45:58 GMT</pubDate><guid isPermaLink="true">https://www.krekr.nl/content/hexuploader-mirror/</guid></item><item><title>Develop AI Cover letter generator app in Flask using Gemini API</title><link>https://blog.adnansiddiqi.me/develop-ai-cover-letter-generator-app-in-flask-using-gemini-api/</link><description>&lt;p&gt;This post is part of the GenAI Series. In the previous post, I discussed creating a recipe generator AI app in PHP using OpenAI APIs. In this post, I will explain how to use Google&amp;#8217;s Gemini LLM APIs to create another text-based AI app: an AI cover letter generator. Let&amp;#8217;s dive in! If you are in a hurry, you may check the demo video below: I introduced you to Google&amp;#8217;s LLM API recently. At that time, it was called Bard, which was later rebranded to Gemini. I automated a WordPress blog with Bard APIs to create financial posts. API Key Generation To use Gemini APIs you need an API Key, for that, you will have to visit AI Studio. Once the API Key is generated it will be available for you: Setting up Python SDK Although you can use Gemini&amp;#8217;s REST APIs with the help of  requests library but I will be using the official Python SDK provided by Google. Run the following pip command: pip install -U google-generativeai Setting up Python Flask Since the app is a web app, I am going to use Python Flask for this purpose. If it is not installed you may install it by running the following command in the terminal: pip install --upgrade flask Development Once the required libraries are installed, it&amp;#8217;s time to start working on the app. There are three major parts of any AI generator app: Prompt:  The instructions that will be passed to an LLM to produce the required output. You can usually test it out in an LLM chat interface (ChatGPT, Gemini, Claude) without using any API. User Interface:  This could be either web, mobile, or even CLI. LLM API Integration:  Integration of APIs so that you can use the prompt&amp;#8217;s output in your app. Prompt Engineering So, we are going to perform prompt engineering: writing and optimizing a prompt to suit our needs. Head over to Gemini and test the following prompt: You are a skilled freelancer with expertise in writing cover letters that have successfully helped you secure many jobs. Your task is to craft a compelling cover letter that persuades the client to contact you. It should utilize persuasive techniques and convey that the freelancer is focused on delivering results and helping achieve goals, rather than solely pursuing monetary gain. You will be provided with information in JSON format, which will serve as the foundation for constructing the cover letter &amp;#8220;` { &amp;#8220;Name&amp;#8221;: &amp;#8220;Adnan Siddiqi&amp;#8221;, &amp;#8220;JobTitle&amp;#8221;: &amp;#8220;AI Engineer&amp;#8221;, &amp;#8220;PrimarySkills&amp;#8221;: [&amp;#8220;OpenAI&amp;#8221;, &amp;#8220;Prompt Engineering&amp;#8221;, &amp;#8220;LLMs&amp;#8221;, &amp;#8220;Python&amp;#8221;], &amp;#8220;Experience&amp;#8221;: 7, &amp;#8220;ClientJobPost&amp;#8221;: &amp;#8220;Needs to hire 2 Freelancers\nI&amp;#8217;m looking for a tech lead to help us with the technical aspects of our AI and full-stack projects.\n\nThe work would involve\n- Creating tech architecture, designs and plans\n- Working with the dev team and unblocking them on the tech aspect\n- Preparing project estimates\n- Client meetings and communication\n- Daily team huddles\n\nYou are someone\n- You are a full time freelancer (no full time job or agency at this time)\n- With 8-10+ years of tech experience\n- Hands-on if required on Python, Django, Fast, React,\n- Solution Architect (AWS, Google, Azure) &amp;#8211; one cloud is fine\n- Have knowledge of AI, GPT, LLM and Agents &amp;#8211; this is a bonus\n- Write \&amp;#8221;tech lead\&amp;#8221; on top so I know you have read till here\n\nWe are starting on new projects and our existing tech lead is busy with some of our other projects.&amp;#8221;, &amp;#8220;RelevantProjects&amp;#8221;: [&amp;#8220;AI Recipe Generator&amp;#8221;], &amp;#8220;ClientName&amp;#8221;: &amp;#8220;Acme Inc&amp;#8221;, &amp;#8220;StartDate&amp;#8221;: &amp;#8220;2024-11-21&amp;#8221;, &amp;#8220;Deadline&amp;#8221;: &amp;#8220;2024-12-28&amp;#8221;, &amp;#8220;Tone&amp;#8221;: &amp;#8220;conversational&amp;#8221; } &amp;#8220;` The output must be in JSON format contain the coverletter text in &amp;#8220;text&amp;#8221; field. I have used this UpWork job post as an example for testing the prompt. When you run the prompt it prints: { "text": "Hi Acme Inc,\n\nI'm Adnan Siddiqi, an AI Engineer with 7 years of experience. I'm writing to express my keen interest in the tech lead position you've posted. I'm a full-time freelancer, ready to dedicate my skills and expertise to your AI and full-stack projects.\n\nI've been closely following the advancements in AI, particularly in the areas of OpenAI, Prompt Engineering, and LLMs. My experience in Python and my hands-on approach to problem-solving make me confident in my ability to deliver exceptional results. \n\nI've successfully led teams in the past and have a proven track record of creating innovative solutions. I'm excited about the opportunity to contribute to your projects, from crafting robust tech architectures to collaborating seamlessly with your dev team. I'm also proficient in project estimation and client communication, ensuring that projects are delivered on time and within budget.\n\nI'm particularly interested in the AI Recipe Generator project. I believe my skills in AI and Python can be leveraged to create a truly innovative and user-friendly solution. \n\nI'm eager to discuss how my skills and experience can benefit your team. Let's connect to explore this opportunity further.\n\nThanks,\nAdnan Siddiqi" } Looks good!  At the UI end, we will be parsing the JSON extracting the data from the text field and displaying it. UI Design This is a simple bootstrap CSS-based web form that accepts different inputs. Below is the Flask code for the relevant route: @app.route('/submit', methods=['POST']) def cover_letter_form(): if request.method == 'POST': COVER_LETTER_PROMPT = """ You are a skilled freelancer with expertise in writing cover letters that have successfully helped you secure many jobs. Your task is to craft a compelling cover letter that persuades the client to contact you. It should utilize persuasive techniques and convey that the freelancer is focused on delivering results and helping achieve goals, rather than solely pursuing monetary gain. You will be provided with information in JSON format, which will serve as the foundation for constructing the cover letter: {{ "Name": "{name}", "JobTitle": "{jobTitle}", "PrimarySkills": {primarySkills}, "Experience": {experience}, "ClientJobPost": "{clientJobPost}", "RelevantProjects": {relevantProjects}, "ClientName": "{clientName}", "StartDate": "{startDate}", "Deadline": "{deadline}", "Tone": "{tone}" }} """ data = request.get_json() # Format the prompt with the data formatted_prompt = COVER_LETTER_PROMPT.format( name=data['name'], jobTitle=data['jobTitle'], primarySkills=str(data['primarySkills']), # Convert list to string experience=data['experience'], clientJobPost=data['clientJobPost'].replace("\n", "\\n"), # Escape newlines relevantProjects=str(data['relevantProjects']), # Convert list to string clientName=data['clientName'], startDate=data['startDate'], deadline=data['deadline'], tone=data['tone'] ) generation_config = { "response_schema": content.Schema( type=content.Type.OBJECT, properties={ "answer": content.Schema( type=content.Type.STRING, ), }, ), "response_mime_type": "application/json", } genai.configure(api_key=os.environ["GEMINI_KEY"]) model = genai.GenerativeModel( 'gemini-1.5-flash', generation_config=generation_config ) response = model.generate_content(formatted_prompt) # Example: Print the received data (replace with processing logic) print(f"Name: {data.get('name')}") print(f"Job Title: {data.get('jobTitle')}") print(f"Primary Skills: {data.get('primarySkills')}") print(f"Experience: {data.get('experience')}") print(f"Client Job Post: {data.get('clientJobPost')}") print(f"Relevant Projects: {data.get('relevantProjects')}") print(f"Client Name: {data.get('clientName')}") print(f"Start Date: {data.get('startDate')}") print(f"Deadline: {data.get('deadline')}") print(f"Tone: {data.get('tone')}") return jsonify(response.text) Calling the generate_content method with the required parameters it returns the JSON payload. In the /submit route it is returning JSON data that is later called via AJAX $.ajax({ url: '/submit', // Adjust URL as needed type: 'POST', contentType: 'application/json', data: JSON.stringify(formData), success: function(response) { const data = JSON.parse(response) let answer = data.answer answer = answer.replace(/\n/g, '&amp;#60;br&amp;#62;') $('#responseOutput').html(answer) $('#responseContainer').show(); $("#wait").hide() }, error: function(xhr, status, error) { $('#responseOutput').text(`Error: ${xhr.responseText}`); $("#wait").hide() $('#responseContainer').show(); } }); Simple yet amazing, No? Conclusion In this post, we explored how you can integrate Google Gemini LLM APIs with your app to add intelligence to it. As always, the code is available on GitHub. Looking to create something similar or even more exciting? Schedule a meeting or email me at kadnan @ gmail.com. If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/develop-ai-cover-letter-generator-app-in-flask-using-gemini-api/"&gt;Develop AI Cover letter generator app in Flask using Gemini API&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Tue, 19 Nov 2024 09:49:04 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/develop-ai-cover-letter-generator-app-in-flask-using-gemini-api/</guid></item><item><title>Handling tarballs in Python</title><link>https://heitorpb.github.io/bla/tarballing-in-python/</link><description>Need to work with tar files in Python? Here's how to create and extract compressed tarballs effortlessly.</description><author>Heitor's log</author><pubDate>Tue, 19 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://heitorpb.github.io/bla/tarballing-in-python/</guid></item><item><title>Data Loading - Daft</title><link>https://blog.londogard.com/posts/2024-10-24-data-loading-daft/</link><description>&lt;p&gt;I know I’ve been praising &lt;code&gt;polars&lt;/code&gt; a lot lately, and I’m still in love. &lt;code&gt;polars&lt;/code&gt; will be my continued go-to library for Data Analysis of Tabular data, and when building ETL (data pipelines) in 99% of the cases.&lt;/p&gt;
&lt;p&gt;However, when you work with Deep Learning and multi-modal data you need something to take the data from your Delta Lake, or wherever you store your data, and supply it to the model. That’s where tools like &lt;code&gt;daft&lt;/code&gt; can shine.&lt;/p&gt;
&lt;p&gt;These Data Loading and Processing steps need to be highly optimized to utilize the underlying compute optimally, not wasting $$ on unused GPU’s. The jobs should keep a high % utilization and not be bound by I/O or CPU. In other words: &lt;em&gt;you want to always have data ready when the GPU has time to process more data&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;There’s a lot of tools to do the job, I’ll go through a few of them in upcoming blogs with a simple yet common workload: &lt;em&gt;Image Classification&lt;/em&gt;. Image Classification is simple but the data can quickly grow large to not fit in-memory anymore.&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;Can’t fit in-memory =&amp;gt; I/O needs to be optimized&lt;/li&gt;
&lt;li&gt;Expensive transforms &amp;amp; augmentations =&amp;gt; CPU needs to be optimized&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;An even better example would be &lt;em&gt;Object Detection&lt;/em&gt; as it has ragged (variable) dimensions, i.e.&amp;nbsp;one image has 2 people and another has 1, but it’s a bit more complex data processing. I’ll include this in my next blog where I give “recipes” on how to use Daft.&lt;/p&gt;
&lt;p&gt;Finally, this blog will be quite brief and not as in-depth as I hoped, but there’ll be more blogs coming later!&lt;/p&gt;
&lt;section class="level1" id="daft"&gt;
&lt;h1&gt;Daft&lt;/h1&gt;
&lt;p&gt;Today I’ll introduce one of the newer alternatives in the field, &lt;a href="https://getdaft.io/"&gt;daft&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Daft is what you can only call a merger between &lt;code&gt;polars&lt;/code&gt;, &lt;code&gt;spark&lt;/code&gt; and Deep Learning. If they had been more inspired by &lt;code&gt;polars&lt;/code&gt; in the Developer Experience (DX) I’d have called it a “lovechild”, but for now they don’t have the nice-to-haves like &lt;code&gt;pl.with_column(new_col_name=pl.col("other_col")*2)&lt;/code&gt; named syntax and other things like &lt;code&gt;pl.col("col").replace(dict_to_replace)&lt;/code&gt; and a lot of other things.&lt;/p&gt;
&lt;p&gt;What &lt;em&gt;daft&lt;/em&gt; does have is a &lt;em&gt;multi-modal&lt;/em&gt; namespace, unlike &lt;code&gt;polars&lt;/code&gt; which solely focuses on traditional data-types. This is &lt;em&gt;really&lt;/em&gt; interesting albeit not that fleshed out yet. It’s enjoyable and has potential to grow!&lt;/p&gt;
&lt;p&gt;Further, to quote &lt;em&gt;daft&lt;/em&gt; themselves:&lt;/p&gt;
&lt;blockquote class="blockquote"&gt;
&lt;p&gt;&lt;em&gt;Daft provides a snappy and delightful local interactive experience, but also seamlessly scales to petabyte-scale distributed workloads.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The &lt;em&gt;petabyte-scale&lt;/em&gt; comes from the fact that you can run &lt;em&gt;daft&lt;/em&gt; on top of &lt;em&gt;Ray&lt;/em&gt; which is a distributed framework that tries to take on Spark. It’s famously used at OpenAI while training their models.&lt;/p&gt;
&lt;/section&gt;
&lt;section class="level1" id="coding-with-daft"&gt;
&lt;h1&gt;Coding with Daft&lt;/h1&gt;
&lt;p&gt;Coding with &lt;code&gt;daft&lt;/code&gt; is an experience. I only ran locally but it held up really well to “native” PyTorch, even surpassing it in one case!&lt;/p&gt;
&lt;p&gt;I’ll share my experience and implementations below!&lt;/p&gt;
&lt;section class="level2" id="reading-data"&gt;
&lt;h2 class="anchored"&gt;Reading Data&lt;/h2&gt;
&lt;p&gt;Like most modern projects &lt;em&gt;daft&lt;/em&gt; includes a smooth integration to &lt;em&gt;Apache Arrow&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote class="blockquote"&gt;
&lt;p&gt;Apache Arrow is “The universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Arrow integration gives &lt;em&gt;daft&lt;/em&gt; multiple ways to read a dataset, and the dataset doesn’t even have to be in-memory because of the Arrow data structure which can easily be streamed via “memory-map-mode” (&lt;code&gt;mmap&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;To “read” an Arrow table you simply call &lt;code&gt;from_arrow&lt;/code&gt;, as I do below reading a HuggingFace Datasets Arrow Table.&lt;/p&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb1" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb1-1"&gt;ds_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; daft.from_arrow(ds[&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"train"&lt;/span&gt;].data.table)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;To “read” other formats from disk you simply use &lt;code&gt;read_(delta|csv|...)&lt;/code&gt;, as below.&lt;/p&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb2" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb2-1"&gt;df &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; daft.read_deltalake(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"some-table-uri"&lt;/span&gt;) &lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# read_(csv|parquet|json|...)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Finally it has very tight integration with Ray, which is very neat when you wish to scale to Big Data.&lt;/p&gt;
&lt;/section&gt;
&lt;section class="level2" id="data-transforms---multi-modal-and-whatnot"&gt;
&lt;h2 class="anchored"&gt;Data Transforms - multi-modal and whatnot&lt;/h2&gt;
&lt;p&gt;To modify a DataFrame you work very similar to &lt;code&gt;polars&lt;/code&gt;. There’s &lt;code&gt;Expression&lt;/code&gt;’s which is a way to have a lazy non-evaluated expression, like a SQL query before you run it. I’ve spoken about &lt;code&gt;Expression&lt;/code&gt;’s before and I really love them, they make code decoupling a lot easier and can simplify a query to something beautiful.&lt;/p&gt;
&lt;p&gt;See my example of extracting image from a struct that has a field with bytes.&lt;/p&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb3" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb3-1"&gt;&lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# expression: lazy non-executed method&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb3-2"&gt;extract_img_bytes &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; daft.col(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;).struct.get(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"bytes"&lt;/span&gt;).alias(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb3-3"&gt;&lt;/span&gt;
&lt;span id="cb3-4"&gt;ds_train.select(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"label"&lt;/span&gt;, extract_img_bytes)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;blockquote class="blockquote"&gt;
&lt;p&gt;Select column &lt;code&gt;label&lt;/code&gt; and &lt;code&gt;image&lt;/code&gt;, where &lt;code&gt;image&lt;/code&gt; extracts &lt;code&gt;image.bytes&lt;/code&gt; into &lt;code&gt;image&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;From here I’d like to decode the image into something which we can work with, unlike bytes, and that’s easy using the multi-modal namespace (&lt;code&gt;.image&lt;/code&gt;).&lt;/p&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb4" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb4-1"&gt;img_decode_resize &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; daft.col(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;).image.decode(mode&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"RGB"&lt;/span&gt;).image.resize(&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;224&lt;/span&gt;, &lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;224&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb4-2"&gt;&lt;/span&gt;
&lt;span id="cb4-3"&gt;ds_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds_train.with_column(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;, img_decode_resize)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;blockquote class="blockquote"&gt;
&lt;p&gt;Transforms &lt;code&gt;image&lt;/code&gt; by decoding it into &lt;code&gt;RGB&lt;/code&gt; and then resizing to &lt;code&gt;224x224&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Quite cool right? There’s some great potential here!&lt;/p&gt;
&lt;p&gt;How do we apply more complex operations? UDF’s! It’s just as easy as in &lt;code&gt;polars&lt;/code&gt;, simply call &lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb5" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb5-1"&gt;&lt;span class="kw" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;def&lt;/span&gt; rescale_transpose(x: np.array):&lt;/span&gt;
&lt;span id="cb5-2"&gt;    &lt;span class="cf" style="color: #003B4F; background-color: null; font-weight: bold; font-style: inherit;"&gt;return&lt;/span&gt; (x &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;/&lt;/span&gt; &lt;span class="fl" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;255.0&lt;/span&gt;).transpose(&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;2&lt;/span&gt;, &lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;0&lt;/span&gt;, &lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;1&lt;/span&gt;)&lt;/span&gt;
&lt;span id="cb5-3"&gt;&lt;/span&gt;
&lt;span id="cb5-4"&gt;ds_train.with_column(&lt;/span&gt;
&lt;span id="cb5-5"&gt;    &lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;,&lt;/span&gt;
&lt;span id="cb5-6"&gt;    daft.col(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;).&lt;span class="bu" style="color: null; background-color: null; font-style: inherit;"&gt;apply&lt;/span&gt;(&lt;/span&gt;
&lt;span id="cb5-7"&gt;        rescale_transpose,&lt;/span&gt;
&lt;span id="cb5-8"&gt;        return_dtype&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;daft.DataType.tensor(daft.DataType.float32()),&lt;/span&gt;
&lt;span id="cb5-9"&gt;    ),&lt;/span&gt;
&lt;span id="cb5-10"&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;blockquote class="blockquote"&gt;
&lt;p&gt;Applying a custom transformation. Images are represented as &lt;code&gt;np.array&lt;/code&gt; and you need to define &lt;code&gt;return_dtype&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;With all this available we’re good to go for a Deep Learning training pipeline!&lt;/p&gt;
&lt;/section&gt;
&lt;section class="level2" id="producing-a-pytorch-dataset"&gt;
&lt;h2 class="anchored"&gt;Producing a PyTorch Dataset&lt;/h2&gt;
&lt;p&gt;The final part of our pipeline is to move the data into &lt;code&gt;torch.Tensor&lt;/code&gt;. There’s one big gotcha - don’t apply &lt;code&gt;num_workers&lt;/code&gt; as &lt;em&gt;daft&lt;/em&gt; already applies multi-thread/processing optimizations!&lt;/p&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb6" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb6-1"&gt;ds_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; ds_train.to_torch_iter_dataset()&lt;/span&gt;
&lt;span id="cb6-2"&gt;&lt;/span&gt;
&lt;span id="cb6-3"&gt;&lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;# &lt;/span&gt;&lt;span class="al" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;NOTE&lt;/span&gt;&lt;span class="co" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;: don't apply num_workers even though PyTorch warns!&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb6-4"&gt;dls_train &lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt; torch.utils.data.DataLoader(ds_train, batch_size&lt;span class="op" style="color: #5E5E5E; background-color: null; font-style: inherit;"&gt;=&lt;/span&gt;&lt;span class="dv" style="color: #AD0000; background-color: null; font-style: inherit;"&gt;32&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And that’s a wrap! We got all the steps to finalize the deal. How about a comparison?&lt;/p&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section class="level1" id="mini-benchmark"&gt;
&lt;h1&gt;Mini Benchmark&lt;/h1&gt;
&lt;p&gt;Comparing speeds with “native” PyTorch DataLoaders is interesting and shows that Daft is on-par in speed when using their new &lt;em&gt;native execution engine&lt;/em&gt; (&lt;em&gt;swordfish&lt;/em&gt;). When I increase image size, i.e.&amp;nbsp;larger data to process, I see Daft even surpassing PyTorch DataLoaders (!).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;N.B.&lt;/strong&gt; I’m running the full training from a HuggingFace Dataset backed by Arrow. It’s the same underlying data structure for all tests except “Folder File” one, but things might just be different if we start discussing file-loading (rather than from bytes) or even remote data.&lt;/p&gt;
&lt;section class="level2" id="numbers"&gt;
&lt;h2 class="anchored"&gt;Numbers&lt;/h2&gt;
&lt;table class="caption-top table"&gt;
&lt;colgroup&gt;
&lt;col style="width: 33%;" /&gt;
&lt;col style="width: 13%;" /&gt;
&lt;col style="width: 13%;" /&gt;
&lt;col style="width: 6%;" /&gt;
&lt;col style="width: 18%;" /&gt;
&lt;col style="width: 13%;" /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr class="header"&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Num_worker&lt;/th&gt;
&lt;th&gt;Pin_memory&lt;/th&gt;
&lt;th&gt;Cache&lt;/th&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;strong&gt;Torch Dataset/Loader&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3m20s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;3m26s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;4m9s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Default&lt;/td&gt;
&lt;td&gt;3m44s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;strong&gt;Daft&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;daft-default&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;14m55s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;daft-native&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3m30s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Running on full sized images we get a bit more interesting results:&lt;/p&gt;
&lt;table class="caption-top table"&gt;
&lt;colgroup&gt;
&lt;col style="width: 39%;" /&gt;
&lt;col style="width: 12%;" /&gt;
&lt;col style="width: 12%;" /&gt;
&lt;col style="width: 6%;" /&gt;
&lt;col style="width: 16%;" /&gt;
&lt;col style="width: 11%;" /&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr class="header"&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Num_worker&lt;/th&gt;
&lt;th&gt;Pin_memory&lt;/th&gt;
&lt;th&gt;Cache&lt;/th&gt;
&lt;th&gt;Configuration&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;strong&gt;Full Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;True&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;torch&lt;/td&gt;
&lt;td&gt;4m19s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;strong&gt;Full Size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;daft&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3m49s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="odd"&gt;
&lt;td&gt;&lt;strong&gt;Image Folder &amp;amp; Files (160p)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;torch&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3m31s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr class="even"&gt;
&lt;td&gt;&lt;strong&gt;Image Folder &amp;amp; Files (160p)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;daft&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3m26s&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;To read a file locally using &lt;em&gt;daft&lt;/em&gt; you simply do the same as you’d do with remote.&lt;/p&gt;
&lt;div class="code-copy-outer-scaffold"&gt;&lt;div class="sourceCode" id="cb7" style="background: #f1f3f5;"&gt;&lt;pre class="sourceCode python code-with-copy"&gt;&lt;code class="sourceCode python"&gt;&lt;span id="cb7-1"&gt;df.with_column(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"image"&lt;/span&gt;, daft.col(&lt;span class="st" style="color: #20794D; background-color: null; font-style: inherit;"&gt;"path"&lt;/span&gt;).url.download())&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/section&gt;
&lt;/section&gt;
&lt;section class="level1" id="remote-data"&gt;
&lt;h1&gt;Remote data&lt;/h1&gt;
&lt;p&gt;Working with remote data is a common and interesting use-case. I think based on this research that &lt;em&gt;daft&lt;/em&gt; has a good chance of performing really well, as the local files also did great.&lt;/p&gt;
&lt;/section&gt;
&lt;section class="level1" id="final-thoughts"&gt;
&lt;h1&gt;Final Thoughts&lt;/h1&gt;
&lt;p&gt;Even if &lt;em&gt;daft&lt;/em&gt; has a way to go for Deep Learning training it really holds great promise. If they make the export easier to PyTorch and perhaps add TensorFlow I believe it could grow into a valuable competitor to HuggingFace Datasets et. al.&lt;/p&gt;
&lt;p&gt;As Ray is what drives OpenAI’s training I believe Daft stands on some really good scalable underlying tech and can perhaps be what joins Data Engineering and Data Science together as one, for real - a big leap forward!&lt;/p&gt;
&lt;p&gt;Thanks for this time, Hampus&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extra:&lt;/strong&gt; all code is available on the git-repo for this blog, see &lt;code&gt;code/data_loading&lt;/code&gt;.&lt;/p&gt;


&lt;/section&gt;</description><author>Londogard Blog</author><pubDate>Tue, 19 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.londogard.com/posts/2024-10-24-data-loading-daft/</guid></item><item><title>Embeddings Clustering</title><link>https://www.danielcorin.com/til/embeddings/embeddings-clustering/</link><description>Embeddings Clustering</description><author>Thought Eddies</author><pubDate>Tue, 19 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/embeddings/embeddings-clustering/</guid></item><item><title>Stay in the game</title><link>https://ilearnt.com/blog/stayinthegame/</link><description>&lt;p&gt;In a recent interview Mo Salah shared some advice he received from the ex-Arsenal manager Arsene Wenger that has stuck with him.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Mon, 18 Nov 2024 19:19:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/stayinthegame/</guid></item><item><title>Inside-out templates</title><link>https://honza.pokorny.ca/2024/11/inside-out-templates/</link><description>&lt;p&gt;I have been thinking about literate programming and markup languages for authoring documents.  How can we write a document once and easily publish it in different formats?  Markdown is user-friendly but bare-bones.  XML does what we want but no one wants to write XML.  I was reminded of &lt;a href="https://docs.racket-lang.org/pollen/"&gt;Pollen&lt;/a&gt;.  It promises to do what I want.  Plus it&amp;rsquo;s a cool Lisp thing.  But Lisp isn&amp;rsquo;t everyone&amp;rsquo;s thing.  Then I had a flash of inspiration: what if we could abuse some Go templates?&lt;/p&gt;
&lt;p&gt;Instead of using Go templates as &lt;em&gt;templates&lt;/em&gt;, we can use them as an authoring language.  Markdown on steroids.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s imagine a document, say &lt;code&gt;chapter.txt&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-txt"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{{ title "Introduction" }}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Before we can start thinking about recursion we must first think about recursion.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;The Wikipedia article on the topic {{ link "says" "https://en.wikipedia.org/wiki/Recursion" }} that...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s not as terse as Markdown but &lt;em&gt;way&lt;/em&gt; nicer than XML.&lt;/p&gt;
&lt;p&gt;How do we process it?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-go"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;contentsBytes&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; err &lt;span style="color: #81a1c1;"&gt;:=&lt;/span&gt; os&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;ReadFile&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"chapter.txt"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; err &lt;span style="color: #81a1c1;"&gt;!=&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;nil&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;return&lt;/span&gt; err
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;contents &lt;span style="color: #81a1c1;"&gt;:=&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;contentsBytes&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;t&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; err &lt;span style="color: #81a1c1;"&gt;:=&lt;/span&gt; template&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;New&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"chapter"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;).&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;Funcs&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;funcs&lt;span style="color: #eceff4;"&gt;).&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;Parse&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;contents&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; err &lt;span style="color: #81a1c1;"&gt;!=&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;nil&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;return&lt;/span&gt; err
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;t&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;Execute&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;os&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;Stdout&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;nil&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pretty standard Go stuff.  Read the file, create a new template, and print the result to standard output.  But what is this &lt;code&gt;funcs&lt;/code&gt;?  You can define custom functions that will be available in your template (our document).  This is where we can define our &lt;code&gt;title&lt;/code&gt; and &lt;code&gt;link&lt;/code&gt; functions.  Let&amp;rsquo;s say we want to publish our chapter as HTML:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-go"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;funcs &lt;span style="color: #81a1c1;"&gt;:=&lt;/span&gt; template&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;FuncMap&lt;span style="color: #eceff4;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #a3be8c;"&gt;"title"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;:&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;func&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;text &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #81a1c1; font-weight: bold;"&gt;return&lt;/span&gt; fmt&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;Sprintf&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"&amp;lt;h1&amp;gt;%s&amp;lt;/h1&amp;gt;"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; text&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #a3be8c;"&gt;"link"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;:&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;func&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;label &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; url &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #81a1c1; font-weight: bold;"&gt;return&lt;/span&gt; fmt&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;Sprintf&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;`&amp;lt;a href="%s"&amp;gt;%s&amp;lt;/a&amp;gt;`&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; url&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; label&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Running it will produce:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-html"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;h1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;&amp;gt;&lt;/span&gt;Introduction&lt;span style="color: #eceff4;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;h1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Before we can start thinking about recursion we must first think about recursion.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;The Wikipedia article on the topic &lt;span style="color: #eceff4;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;a&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;href&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"https://en.wikipedia.org/wiki/Recursion"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;&amp;gt;&lt;/span&gt;says&lt;span style="color: #eceff4;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;a&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;&amp;gt;&lt;/span&gt; that...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s much nicer than using some custom string search and replace because the Go standard library handles all of the replacing for you: they have done the hard work of finding the edge-cases for you and handling them.&lt;/p&gt;
&lt;p&gt;OK, onward: what about Markdown?  Let&amp;rsquo;s make this a bit harder and use footnote-style links at the bottom of the page:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-go"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;links &lt;span style="color: #81a1c1;"&gt;:=&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[]&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;linkCounter &lt;span style="color: #81a1c1;"&gt;:=&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;funcs &lt;span style="color: #eceff4;"&gt;=&lt;/span&gt; template&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;FuncMap&lt;span style="color: #eceff4;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;	&lt;span style="color: #a3be8c;"&gt;"title"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;:&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;func&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;text &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;		&lt;span style="color: #81a1c1; font-weight: bold;"&gt;return&lt;/span&gt; fmt&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;Sprintf&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"%s\n%s"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; text&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; strings&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;Repeat&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"="&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;len&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;text&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;	&lt;span style="color: #eceff4;"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;	&lt;span style="color: #a3be8c;"&gt;"link"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;:&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;func&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;label &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; url &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;string&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;		id &lt;span style="color: #81a1c1;"&gt;:=&lt;/span&gt; linkCounter
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;		linkCounter&lt;span style="color: #81a1c1;"&gt;++&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;		links &lt;span style="color: #eceff4;"&gt;=&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;append&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;links&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; fmt&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;Sprintf&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"[%d]: %s"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; id&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; url&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;		&lt;span style="color: #81a1c1; font-weight: bold;"&gt;return&lt;/span&gt; fmt&lt;span style="color: #eceff4;"&gt;.&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;Sprintf&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"[%s][%d]"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; label&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; id&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;	&lt;span style="color: #eceff4;"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The links will be collected in a slice and can be appended in a convenient place.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-markdown"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Introduction
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;============
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;Before we can start thinking about recursion we must first think about recursion.
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;The Wikipedia article on the topic [says][0] that...
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;[0]: https://en.wikipedia.org/wiki/Recursion
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;You can extend this in a million different ways.  Need academic references?  Hook this up to bibtex or Zotero.  Images?  Gnuplot graphs that are generated on the fly but different for webpages and ebooks?  Syntax highlighting for code snippets?&lt;/p&gt;
&lt;p&gt;Honza, did you just invent literate programming?!&lt;/p&gt;</description><author>Honza Pokorný</author><pubDate>Mon, 18 Nov 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://honza.pokorny.ca/2024/11/inside-out-templates/</guid></item><item><title>Choose Boring Technology</title><link>https://prashamhtrivedi.in/boringtech/</link><description>Let’s say every company gets about three innovation tokens. You can spend these however you want, but the supply is fixed for a long while. You might get a few more after you achieve a certain level of stability and maturity, but the general tendency is to overestimate the contents of your wallet. Clearly this model is approximate, but I think it helps.
You can replenish the tokens if you can to hire more people who brings new innovation tokens with them.</description><author>Prasham H Trivedi</author><pubDate>Mon, 18 Nov 2024 09:23:01 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/boringtech/</guid></item><item><title>Typst Business Card Templates</title><link>https://briansunter.com/projects/typst-business-cards</link><description>A collection of professionally-designed business card templates built with Typst, featuring multiple styles from minimalist to luxury.</description><author>Brian Sunter</author><pubDate>Mon, 18 Nov 2024 06:22:08 GMT</pubDate><guid isPermaLink="true">https://briansunter.com/projects/typst-business-cards</guid></item><item><title>Optimizing a go package by 38147125738.8x</title><link>https://xnacly.me/posts/2024/optimizing-a-dependency/</link><description>Investigating performance issues in a go package for open port lookup</description><author>xnacly - blog</author><pubDate>Mon, 18 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2024/optimizing-a-dependency/</guid></item><item><title>Just Do Something Already</title><link>https://benjcal.space/blog/just-do-something-already/</link><description>&lt;blockquote&gt;
&lt;p&gt;This is a repost of an article I originally published on Medium in November of 2020. I'm moving it here to consolidate all my writing in one place.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;source type="image/avif" /&gt;&lt;img alt="" height="347" src="https://benjcal.space/blog/just-do-something-already/OmCDLfUEIE-512.webp" width="512" /&gt;&lt;/p&gt;
&lt;p&gt;Have you had an idea? A deep seated conviction that things could be better? That &lt;em&gt;you&lt;/em&gt; could bring about that change for the better?
But, on the other hand, has your idea been with you for years with nothing to show for it? Have you seen others bring about their ideas? Maybe you have even tried a few times to do the same but haven’t or couldn’t? Then I’m writing this for &lt;em&gt;you&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This is the story of an idea (&lt;a href="https://gradely.pages.dev/"&gt;&lt;strong&gt;Gradely&lt;/strong&gt;&lt;/a&gt;, use &lt;code&gt;bali5more&lt;/code&gt; as a key to test). It is not a success story in which I tell you how you should do things just the way I did them and that success is “guaranteed.” It is not a failure story either in which I reflect on all the things I did wrong and what I learned from the experience. It is the story of the ongoing struggle of being stuck, and now finally doing something about the idea!&lt;/p&gt;
&lt;p&gt;You see, I believe that many of us have thought about doing something, but are paralyzed by a myriad of excuses and factors, thinking that we don’t have what it takes and just conforming to do nothing. And I want to convince you otherwise!&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I have always thought that one man of &lt;strong&gt;tolerable abilities&lt;/strong&gt; may work great changes, and accomplish great affairs among mankind… -Benjamin Franklin&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="the-entrepreneurship-bug"&gt;The entrepreneurship bug&lt;/h2&gt;
&lt;p&gt;I don’t know the exact circumstances that lead a person to believe that &lt;em&gt;they&lt;/em&gt; can bring about change, that &lt;em&gt;li’l ol’ me&lt;/em&gt; can do something of value, but I can share how it happened to me.&lt;/p&gt;
&lt;p&gt;In 2012, I started a masters degree in leadership/philosophy and got a work-study position for the department of distant education of my school. I had never worked before in tech but ever since I can remember I have been involved with it. From breaking my mom’s irons to see what was in there (…I didn’t know about screwdrivers back then!), to building a crystal radio and experiencing the magic of “the waves,” to going down the Linux train, to electronics and more. I got my hands on whatever books I could and tried all kinds of tech things even from my childhood.&lt;/p&gt;
&lt;p&gt;Thus, I had no &lt;em&gt;“official”&lt;/em&gt; qualifications for that job except what I had learned and the crazy notion that if I came across something I didn’t know, I could just learn it. Nowadays I just say that &lt;strong&gt;everything is &lt;em&gt;figureoutable&lt;/em&gt;.&lt;/strong&gt; But I digress.&lt;/p&gt;
&lt;p&gt;It was at that job that I heard how much the university was paying for a service. I was appalled! The software that they were paying for was open-source/free and that meant that they were pretty much just paying for servers! To my naive mind it sounded like I could provide those services, charge them half what they were paying and with 4–5 clients I would be set for life! And just like that I was bitten by the entrepreneurship bug and have been affected ever since.&lt;/p&gt;
&lt;p&gt;From there the idea narrowed down to a general &lt;em&gt;“I want to do something with technology for education”&lt;/em&gt; to &lt;em&gt;“I want to build a gradebook that doesn’t suck!”&lt;/em&gt; But it would be years before anything happened.&lt;/p&gt;
&lt;h2 id="and-then-what"&gt;And then… what?&lt;/h2&gt;
&lt;p&gt;And then… I did nothing. Well, not &lt;em&gt;nothing&lt;/em&gt; nothing. I Googled about how to start a business… and was inundated by the overwhelming amount of information that I found. It wasn’t just the sheer volume but how all-over-the-place it was. There were stories of &lt;em&gt;successful&lt;/em&gt; people that had found success doing the complete opposite things! I heard that they way to go was with sponsors (and spent $500 on a course to learn more about it). I heard that the way to go was with an accelerator and VCs. I heard that they way to go was being bootstrapped. I heard that B2B was king, that B2C has more volume and market… I read and read and read, maybe 30+ books, listened to a bunch of podcasts, read blog posts, HN stories, etc. etc.&lt;/p&gt;
&lt;p&gt;But one thing was true during all of this: &lt;strong&gt;I never felt ready.&lt;/strong&gt; It seemed to me that the people doing these things, starting organizations and business, bringing about change, had it all together. I thought these people just knew what to do and did it.&lt;/p&gt;
&lt;p&gt;And here I was with an idea that I had tried to bring to fruition multiple times, that I had talked over with many educators and received good feedback, and yet… nothing. Here I was with &lt;em&gt;“tolerable abilities”&lt;/em&gt; to build said idea and still I had done nothing, still felt unprepared, still felt &lt;em&gt;unworthy&lt;/em&gt; if you may. How dare I think I could bring about change?! How dare I think people would listen to me?! And for 8 years I was stuck in that quicksand.&lt;/p&gt;
&lt;p&gt;And then it hit me. I could spend the next eight years in that quick sand, with the dreaded “what if” question festering in my mind, or I could &lt;em&gt;just do something already!&lt;/em&gt; And it was that question that revealed the culprit that had been holding me back. It was not a lack of technical skills, or the lack of some intrinsic personality trait, or some genetically bestowed advantage. No, the culprit was fear, plain raw simple old fear. Fear that whatever I do won’t matter. Fear that the people I wanted to serve would reject me. Fear that all I could do was spend another eight years doing what I had done - nothing - and in the end accomplish nothing. Fear fear fear…&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Oh, such an old enemy, but how well it still fools us! How well it masquerades itself and blames &lt;em&gt;us&lt;/em&gt; for its deficiencies!&lt;/p&gt;
&lt;p&gt;When I finally realized all the fear I had and started looking around and listening, I was surprised by what I heard. Everybody else was also afraid!! My boss was afraid, the CEO of the company was afraid, the entrepreneur that I so admired was also afraid!&lt;/p&gt;
&lt;p&gt;But you know what? It is those that tell their fears to shut up who are the ones that bring about change! The ones who take their ideas and make them a reality and make the world, even if only a little, a better world!&lt;/p&gt;
&lt;p&gt;And here you are reading my feeble attempt to tell my feats to shut up. My attempt at believing that &lt;em&gt;li’l ol’ me&lt;/em&gt; can bring about a change, can make a difference in the lives of those I want to serve. That &lt;em&gt;I&lt;/em&gt; can make the world, even if only a little, a better world.&lt;/p&gt;
&lt;p&gt;And it is my strong belief that &lt;em&gt;you&lt;/em&gt; can too! I don’t know your circumstances, your fears, your challenges, but I do believe that you can overcome them as those before you have done. I believe in you and I want you to believe in you, too!&lt;/p&gt;
&lt;p&gt;And then there’s this quote by Teddy Roosevelt…&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“It is not the critic who counts; not the man who points out how the strong man stumbles, or where the doer of deeds could have done them better. The credit belongs to the man who is actually in the arena, whose face is marred by dust and sweat and blood; who strives valiantly; who errs, who comes short again and again, because there is no effort without error and shortcoming; but who does actually strive to do the deeds; who knows great enthusiasms, the great devotions; who spends himself in a worthy cause; who at the best knows in the end the triumph of high achievement, and who at the worst, if he fails, at least fails while daring greatly, so that his place shall never be with those cold and timid souls who neither know victory nor defeat.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="out-there"&gt;Out there&lt;/h2&gt;
&lt;p&gt;So this is the beginning of that journey, of &lt;em&gt;“just doing something about it!”,&lt;/em&gt; my attempt to put myself out there. What will come of it is still to be seen, but I can tell you this: &lt;em&gt;“out there”&lt;/em&gt; the sun is shining! &lt;em&gt;“Out there”&lt;/em&gt; the possibilities are endless! &lt;em&gt;“Out there”&lt;/em&gt; is still full of dangers and risks, but you can overcome them! &lt;em&gt;“Out there”&lt;/em&gt; the world is waiting for you!&lt;/p&gt;</description><author>Ben's Web Space</author><pubDate>Mon, 18 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://benjcal.space/blog/just-do-something-already/</guid></item><item><title>Native dual-range input</title><link>https://muffinman.io/blog/native-dual-range-input/</link><description>&lt;article class="article"&gt;
&lt;p&gt;I just released &lt;a href="https://github.com/stanko/dual-range-input"&gt;@stanko/dual-range-input&lt;/a&gt; - a native dual-range input. Here is how it looks with the default styles:&lt;/p&gt;
&lt;div class="demo"&gt;&lt;div class="dual-range-input"&gt;&lt;input max="100" min="0" step="1" type="range" value="25" /&gt;&lt;input max="100" min="0" step="1" type="range" value="75" /&gt;&lt;/div&gt;&lt;div class="values"&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The &amp;quot;native&amp;quot; part is somewhat open for discussion. I call it native because the library uses two native HTML range inputs. This means that all of the native interactions and accessibility features are preserved.&lt;/p&gt;
&lt;p&gt;Native inputs allow us not to reinvent the wheel. There is about &lt;a href="https://cdn.jsdelivr.net/npm/@stanko/dual-range-input/dist/index.js"&gt;fifty lines of JavaScript&lt;/a&gt; to synchronize the inputs, along with some CSS to ensure everything looks polished.&lt;/p&gt;
&lt;p&gt;In my book, that is &lt;em&gt;native enough&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="why"&gt;Why &lt;a class="anchor-link" href="#why"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I create &lt;a href="/art"&gt;my generative drawings&lt;/a&gt;, I use a tool I built myself. This tool includes a UI for tweaking parameters, and I often have minimum and maximum sliders for certain parameters. I thought it would be nice to have a dual-range slider for these. However, most existing solutions rely heavily on JavaScript and reimplement dragging and accessibility features.&lt;/p&gt;
&lt;p&gt;So, I set my own set of requirements:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It should use native HTML range inputs.&lt;/li&gt;
&lt;li&gt;When you click on the track, the closer of the two thumbs should jump to that value.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hopefully, after you read these two requirements, my solution will make sense.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;How it works &lt;a class="anchor-link" href="#how-it-works"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are two inputs placed next to each other. When either of the inputs is changed, the library calculates a midpoint between the two selected values. Then the &lt;code&gt;min&lt;/code&gt; and &lt;code&gt;max&lt;/code&gt; attributes are set to the midpoint, and the width of both inputs is updated to match.&lt;/p&gt;
&lt;p&gt;Here is an unstyled example, which will hopefully illustrate this well:&lt;/p&gt;
&lt;div class="demo demo--blank"&gt;&lt;div class="inputs"&gt;&lt;input list="tickmarks" max="50" min="0" step="1" type="range" value="25" /&gt;&lt;input list="tickmarks" max="50" min="0" step="1" type="range" value="75" /&gt;&lt;/div&gt;&lt;div class="values"&gt;&lt;/div&gt;&lt;datalist id="tickmarks"&gt;&lt;/datalist&gt;&lt;/div&gt;
&lt;p&gt;Even like this, it works reasonably well. We&amp;#x27;ll style it later to make it look nicer.&lt;/p&gt;
&lt;h3 id="resizing-the-inputs"&gt;Resizing the inputs &lt;a class="anchor-link" href="#resizing-the-inputs"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There&amp;#x27;s a small trick involved in calculating input widths. This is because the range input&amp;#x27;s track is actually shorter than the input&amp;#x27;s total width. All browsers leave enough space on the sides so the thumb doesn&amp;#x27;t stick out.&lt;/p&gt;
&lt;p&gt;Here is a screenshot from Firefox (other browsers work similarly), where you can see that the track is shorter than the width. I&amp;#x27;ve emphasized the space the browser leaves for the thumb.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot showing that the range input&amp;#x27;s track is shorter than its total width" class="image" height="130" src="./thumb-width.png" width="790" /&gt;&lt;/p&gt;
&lt;p&gt;If we take an example where the inputs need to be in a 1:3 ratio, simply setting their widths to 25% and 75% isn&amp;#x27;t enough. We also need to account for the thumb width. Instead of calculating the exact ratio, I simplified the math by adding the thumb width to each input&amp;#x27;s width:&lt;/p&gt;
&lt;pre class="language-scss"&gt;&lt;code class="language-scss code-highlight"&gt;&lt;span class="code-line"&gt;&lt;span class="token selector"&gt;input:first-child &lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt; &lt;span class="token property"&gt;width&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; &lt;span class="token function"&gt;calc&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;25% &lt;span class="token operator"&gt;+&lt;/span&gt; &lt;span class="token function"&gt;var&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;--dri-thumb-width&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token punctuation"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token selector"&gt;input:last-child &lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt; &lt;span class="token property"&gt;width&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; &lt;span class="token function"&gt;calc&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;75% &lt;span class="token operator"&gt;+&lt;/span&gt; &lt;span class="token function"&gt;var&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;--dri-thumb-width&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token punctuation"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you thought, &lt;em&gt;Wait, this adds up to more than 100%&lt;/em&gt;, you&amp;#x27;d be 100% right. That&amp;#x27;s why I applied a small trick: I added padding to the inputs&amp;#x27; wrapper to accommodate the extra width for the thumbs.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Screenshot showing padding on the wrapper to accommodate extra width" class="image" height="254" src="./padding.png" width="1220" /&gt;&lt;/p&gt;
&lt;p&gt;This makes the math simpler while keeping the input sizing correct. It took me forever to explain this properly, and I&amp;#x27;m still not sure if I succeeded.&lt;/p&gt;
&lt;h3 id="move-the-thumb-closer-to-the-click"&gt;Move the thumb closer to the click &lt;a class="anchor-link" href="#move-the-thumb-closer-to-the-click"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Because the inputs are resized to meet at the midpoint, whenever you click between the thumbs, the one closer to the click will move to that value.&lt;/p&gt;
&lt;p&gt;&lt;button class="toggle-debug text-link" type="button"&gt;Toggle the debug mode &lt;span class="hide-when-debug"&gt;on&lt;/span&gt;&lt;span class="show-when-debug"&gt;off&lt;/span&gt;&lt;/button&gt; and the midpoint will be easier to see.&lt;/p&gt;
&lt;div class="demo demo--purple"&gt;&lt;div class="dual-range-input"&gt;&lt;input max="20" min="0" step="1" type="range" value="4" /&gt;&lt;input max="20" min="0" step="1" type="range" value="5" /&gt;&lt;/div&gt;&lt;div class="values"&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;If there&amp;#x27;s an odd number of steps between the thumbs, the last-used input is favored. Try it out with debug mode on, and you&amp;#x27;ll see what I mean.&lt;/p&gt;
&lt;p&gt;With that, both requirements are satisfied. The only thing left is to style it properly.&lt;/p&gt;
&lt;h2 id="styling"&gt;Styling &lt;a class="anchor-link" href="#styling"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;All browsers allow us to style range inputs using CSS. That made styling of the tracks and thumbs pretty straightforward. I just ensured that the tracks didn&amp;#x27;t have a border radius in the middle where they connect.&lt;/p&gt;
&lt;div class="demo demo--thick-no-gradient"&gt;&lt;div class="dual-range-input"&gt;&lt;input max="100" min="0" step="1" type="range" value="25" /&gt;&lt;input max="100" min="0" step="1" type="range" value="75" /&gt;&lt;/div&gt;&lt;div class="values"&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 id="theming"&gt;Theming &lt;a class="anchor-link" href="#theming"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I exposed several variables to make theming easier. Here&amp;#x27;s the complete list with their default values:&lt;/p&gt;
&lt;pre class="language-scss"&gt;&lt;code class="language-scss code-highlight"&gt;&lt;span class="code-line"&gt;&lt;span class="token selector"&gt;.dual-range-input &lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-height&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; 1.5rem&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-thumb-width&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; 1.25rem&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-thumb-height&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; 1.25rem&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-thumb-color&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; #ddd&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-thumb-hover-color&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; #a8d5ff&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-thumb-active-color&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; #4eaaff&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-thumb-border-color&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; &lt;span class="token function"&gt;rgba&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;0&lt;span class="token punctuation"&gt;,&lt;/span&gt; 0&lt;span class="token punctuation"&gt;,&lt;/span&gt; 0&lt;span class="token punctuation"&gt;,&lt;/span&gt; 0.1&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-thumb-border-radius&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; 1rem&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-track-height&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; 0.25rem&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-track-color&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; #ccc&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-track-filled-color&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; #0084ff&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;--dri-track-border-radius&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; 1rem&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token punctuation"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To create your own theme, simply override these variables.&lt;/p&gt;
&lt;h3 id="gradients"&gt;Gradients &lt;a class="anchor-link" href="#gradients"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One thing I thought was cool is how I used CSS gradients to paint the selected range in both inputs. I set the gradients to use the &lt;code&gt;--dri-gradient-position&lt;/code&gt; variable, then updated that variable in the code along with the widths.&lt;/p&gt;
&lt;p&gt;Here&amp;#x27;s how the CSS looks for one of the inputs:&lt;/p&gt;
&lt;pre class="language-scss"&gt;&lt;code class="language-scss code-highlight"&gt;&lt;span class="code-line"&gt;&lt;span class="token selector"&gt;input:first-child::-moz-range-track &lt;/span&gt;&lt;span class="token punctuation"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token property"&gt;background-image&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; &lt;span class="token function"&gt;linear-gradient&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;    to right&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;    &lt;span class="token function"&gt;var&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;--dri-track-color&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token function"&gt;var&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;--dri-gradient-position&lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;    &lt;span class="token function"&gt;var&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;--dri-track-filled-color&lt;span class="token punctuation"&gt;)&lt;/span&gt; &lt;span class="token function"&gt;var&lt;/span&gt;&lt;span class="token punctuation"&gt;(&lt;/span&gt;--dri-gradient-position&lt;span class="token punctuation"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;  &lt;span class="token punctuation"&gt;)&lt;/span&gt;&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token punctuation"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Again, &lt;button class="toggle-debug text-link" type="button"&gt;toggling the debug mode on&lt;/button&gt; and the semi-transparent thumbs will make the gradients easier to see.&lt;/p&gt;
&lt;div class="demo demo--thick"&gt;&lt;div class="dual-range-input"&gt;&lt;input max="100" min="0" step="1" type="range" value="25" /&gt;&lt;input max="100" min="0" step="1" type="range" value="75" /&gt;&lt;/div&gt;&lt;div class="values"&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 id="conclusion"&gt;Conclusion &lt;a class="anchor-link" href="#conclusion"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I had to write this post as &lt;del&gt;a brain dump&lt;/del&gt; a way to consolidate my thoughts, so I hope it&amp;#x27;s not too convoluted.&lt;/p&gt;
&lt;p&gt;Thank you for following along, and I hope this inspires you to try it out and consider using more native elements before opting for custom libraries.&lt;/p&gt;
&lt;/article&gt;</description><author>Muffin Man</author><pubDate>Mon, 18 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://muffinman.io/blog/native-dual-range-input/</guid></item><item><title>Using Talon in a game jam</title><link>https://ntietz.com/blog/abusing-talon-eyetracker/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;I use &lt;a href="https://talonvoice.com/"&gt;Talon&lt;/a&gt; to control my computer some of the time.
It's mostly voice control, but it has so many other controls built in!
One lets you use an eye tracker as a mouse.
I thought this sounded like a neat interaction for other situations too.
When I mentioned this to a friend, he suggested building a game concept around controlling the game with the eye tracker.&lt;/p&gt;
&lt;p&gt;First, though, I had to do some setup for us: get the eye tracker connected to a Rust game library.
Ultimately, we wound up using pure mouse control—then you don't &lt;em&gt;need&lt;/em&gt; an eye tracker, and if you have one, you can just use Talon to control it—but along the way there were some interesting shenanigans with threads!&lt;/p&gt;
&lt;h1 id="how-do-i-use-an-eye-tracker"&gt;How do I use an eye tracker?&lt;/h1&gt;
&lt;p&gt;It was really tough to figure out how to use my eye tracker, because I'm on Linux!
The SDKs available mostly don't support Linux, or they're C++ and gnarly and I didn't have time to figure out how to do calibration.
I &lt;em&gt;do&lt;/em&gt; have Talon setup for eye tracking, so I thought it could be a neat way to interact with things.&lt;/p&gt;
&lt;p&gt;Talon seems to call into another library, but it wasn't obvious what it was.
I was hoping it would be obvious and I could go use that library, too, but alas.
That's where the investigation ended for me.
I don't think it's a great idea for me to try to poke any deeper, since this is commercial software that's intended to be closed, and we should honor the intentions of the author.&lt;/p&gt;
&lt;p&gt;Instead, I tried using a sidecar for Talon to use the eye tracker!
This was an interesting diversion, though ultimately one that we shouldn't really indulge in: it's probably against Talon's license, and it's certainly against the &lt;em&gt;spirit&lt;/em&gt; of both Talon and the eye trackers' licenses.&lt;/p&gt;
&lt;p&gt;That said, adding modules to Talon is a really valuable thing I tried doing for this!
I've abandoned that approach here, but it was useful to explore in a throwaway project so I can use it when I want to actually expand my tooling.&lt;/p&gt;
&lt;h1 id="talon-user-modules"&gt;Talon user modules&lt;/h1&gt;
&lt;p&gt;One of the beautiful things about Talon is that it's extremely configurable and customizable.
Out of the box, it is quite bare, which is why using the &lt;a href="https://github.com/talonhub/community"&gt;community command set&lt;/a&gt; is highly recommended to start.
In many ways, it's more a workshop for making your own tools, than it is a tool itself.
This gives us a lot of room to build tools &lt;em&gt;within&lt;/em&gt; Talon!&lt;/p&gt;
&lt;p&gt;The first thing I did is created a folder in my talon user directory (for me, at &lt;code&gt;~/.talon/user&lt;/code&gt;).
Then if you add Python code inside that folder, Talon loads it!
This is really neat for building useful accessibility tooling.&lt;/p&gt;
&lt;p&gt;Talon hot reloads plugins, which makes it very easy to iterate on this code.
When I was exploring, my code created a thread.
This thread was problematic, because it wouldn't be stopped when the module was reloaded, so if it held a resource (for example, write access on a file), you'd end up with conflicts.
So I needed a way to stop the thread when we reload it!&lt;/p&gt;
&lt;h1 id="stopping-an-old-thread"&gt;Stopping an old thread&lt;/h1&gt;
&lt;p&gt;To stop our thread, we really ultimately needed a way to access the &lt;em&gt;previous&lt;/em&gt; thread after we reload.
We can't do that by just keeping it in a global in the module, since those are new on each reload.&lt;/p&gt;
&lt;p&gt;I found there's a neat way to do that.
We name the thread when we make it!
Then we can find it by its name later on.&lt;/p&gt;
&lt;p&gt;So I did something like this:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;t &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;threading.Thread(target&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;myfunction)
&lt;/span&gt;&lt;span&gt;t.name &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;my-thread&amp;quot;
&lt;/span&gt;&lt;span&gt;t.data &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;None
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then inside the function, we can assign to our data:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #fa5c4b;"&gt;def &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;myfunction&lt;/span&gt;&lt;span&gt;():
&lt;/span&gt;&lt;span&gt;    t &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;threading.current_thread()
&lt;/span&gt;&lt;span&gt;    t.data &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="font-style: italic; color: #928374;"&gt;# some object we're using
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the next time we reload the module, we can do some cleanup at the top!&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #fa5c4b;"&gt;for &lt;/span&gt;&lt;span&gt;t &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;in &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;threading.enumerate()&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;print&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;(t, t.name)
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if &lt;/span&gt;&lt;span&gt;t.name &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;== &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;my-thread&amp;quot;&lt;/span&gt;&lt;span&gt;:
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;t.data.shutdown()
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;t.join()
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there we have it, we've stopped the previous thread.&lt;/p&gt;
&lt;h1 id="sneak-peek"&gt;Sneak peek&lt;/h1&gt;
&lt;p&gt;I got it integrated into a Rust program on the read side.
Ultimately, we did this by just... using everything as intended, and using the mouse as input.
Since Talon gives us our eye tracker as a mouse, this works for folks with that setup—and it also works if you don't!&lt;/p&gt;
&lt;p&gt;It works, and I can render a red dot wherever I look!
There's some jitter, but it does follow my eyes pretty accurately, and works best in full screen.
Trying to screen record when you're using an eye tracker as your mouse, or debugging it, is pretty funny because you can't look at the controls to trigger things or stop them.
And good luck clicking on the screen recorder.&lt;/p&gt;
&lt;p&gt;Hopefully there'll be more updates on this project after we do our one-day game jam.
And hopefully eventually we'll have a demo that folks can try out, with Talon or a mouse!&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 18 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/abusing-talon-eyetracker/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Host an indie-web yardsale on your /junk page</title><link>https://taylor.town/junk-guide</link><description>You can give that wish a second life.</description><author>taylor.town</author><pubDate>Mon, 18 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/junk-guide</guid></item><item><title>Preserve Child Objects When Parent Objects Are Deleted in Ruby on Rails</title><link>https://nelson.cloud/preserve-child-objects-when-parent-objects-are-deleted-in-ruby-on-rails/?ref=rss</link><description>Use dependent: :nullify in Rails associations to preserve child records when parent objects are deleted.</description><author>Nelson Figueroa</author><pubDate>Mon, 18 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/preserve-child-objects-when-parent-objects-are-deleted-in-ruby-on-rails/?ref=rss</guid></item><item><title>A Poem Of Grief</title><link>https://james.brooks.page/blog/a-poem-of-grief</link><description>A poem I wrote shortly after the passing of my brother and the birth of my first child.</description><author>James Brooks</author><pubDate>Mon, 18 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://james.brooks.page/blog/a-poem-of-grief</guid></item><item><title>Running an LLM locally using Ollama</title><link>https://jasoneckert.github.io/myblog/llms-using-ollama/</link><description>&lt;p&gt;If you are developing or integrating software with a Large Language Model (LLM) to add chatbot or other AI functionality, then you may have the need to host and run an LLM on your local workstation for development or testing. There are several open source LLMs available that you can freely run on your system provided that you have enough resources to do so.&lt;/p&gt;
&lt;p&gt;LLMs are just inert objects that require special software to allow people and other software to interact with it (called &lt;em&gt;&lt;strong&gt;running the model&lt;/strong&gt;&lt;/em&gt;). Unfortunately, setting up the software components to run a model is akin to nailing Jello to a tree.&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Mon, 18 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/llms-using-ollama/</guid></item><item><title>How to join the Wikipedia Discord</title><link>https://river.me/blog/wikipedia-discord/</link><description>Click here if you want to join the Wikipedia Discord server!</description><author>River Writes - A MediaWiki Blog</author><pubDate>Mon, 18 Nov 2024 00:49:19 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/wikipedia-discord/</guid></item><item><title>Memory Eternal, Nikolas Doucet</title><link>https://www.fortressofdoors.com/memory-eternal-nikolas-doucet/</link><description>Nikolas Andreas Doucet, age 8, died painlessly of sudden cardiac arrest on Friday, November 8th, 2024, at 7:03 PM Central time. He was buried on Friday November 16, 2024. The following was the eulogy that I, his father, gave at his graveside.</description><author>Fortress of Doors</author><pubDate>Sun, 17 Nov 2024 23:45:26 GMT</pubDate><guid isPermaLink="true">https://www.fortressofdoors.com/memory-eternal-nikolas-doucet/</guid></item><item><title>Better dmesg in five minutes</title><link>https://purpleidea.com/blog/2024/11/17/better-dmesg/</link><description>&lt;p&gt;I last wrote about &lt;code&gt;dmesg&lt;/code&gt; in &lt;a href="https://purpleidea.com/blog/2016/08/29/live-dmesg-following/"&gt;2016&lt;/a&gt;. It
has mostly not changed since then, but I&amp;rsquo;ve changed my setup slightly. Here&amp;rsquo;s a
short article about what I did so that you can do it too, and so that I can
remember for the next time.&lt;/p&gt;


&lt;br /&gt;&lt;span style="text-decoration: underline; font-weight: bold;"&gt;Previously&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;

&lt;p&gt;Previous I had a bash alias which looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-fallback"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;alias dmesg='dmesg --follow || dmesg'
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;since some machines didn&amp;rsquo;t yet support the &lt;code&gt;--follow&lt;/code&gt; flag.&lt;/p&gt;</description><author>The Technical Blog of James on purpleidea.com</author><pubDate>Sun, 17 Nov 2024 22:52:00 GMT</pubDate><guid isPermaLink="true">https://purpleidea.com/blog/2024/11/17/better-dmesg/</guid></item><item><title>Constraints in Go</title><link>https://heidenstedt.org/links/constraints-in-go/</link><description>&lt;p&gt;
      &lt;em&gt;Best viewed on the &lt;a href="https://heidenstedt.org/links/constraints-in-go/"&gt;original page&lt;/a&gt;, where extended functionality like the
    footnote helper is available.&lt;/em&gt;
    &lt;/p&gt;&lt;p&gt;I want to share this great article about generic coding in Go:  &lt;a href="https://bitfieldconsulting.com/posts/constraints"&gt;https://bitfieldconsulting.com/posts/constraints&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Also &lt;a href="https://go-proverbs.github.io/"&gt;Go Proverbs&lt;/a&gt; is quite nice!&lt;/p&gt;
&lt;p&gt;Ohh and this Blog has now footnotes and popups for footnotes! &lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="https://heidenstedt.org/links/constraints-in-go/#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id="summary-generated"&gt;&lt;a href="https://heidenstedt.org/links/constraints-in-go/#summary-generated"&gt;Summary (Generated):&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;In this tutorial, John Arundel explores constraints in Go, the paradoxical feature of generics that simultaneously limits and expands possibilities. By delving into method sets, type elements, unions, intersections, and approximations, he demonstrates how constraints enable precise operations on type parameters. Whether you&amp;rsquo;re learning to leverage &lt;code&gt;fmt.Stringer&lt;/code&gt;, creating flexible numeric constraints, or handling derived types, this guide unlocks a deeper understanding of Go&amp;rsquo;s generics—perfect for honing your skills or tackling practical challenges in coding.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;This is a footnote!&lt;br /&gt;
And this works even with multiple lines.&lt;br /&gt;
And &lt;code&gt;code&lt;/code&gt; too!&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-js"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a6e22e;"&gt;console&lt;/span&gt;.&lt;span style="color: #a6e22e;"&gt;log&lt;/span&gt;(&lt;span style="color: #e6db74;"&gt;'Hello, World!'&lt;/span&gt;);
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&amp;#160;&lt;a class="footnote-backref" href="https://heidenstedt.org/links/constraints-in-go/#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Mia Heidenstedt</author><pubDate>Sun, 17 Nov 2024 22:44:42 GMT</pubDate><guid isPermaLink="true">https://heidenstedt.org/links/constraints-in-go/</guid></item><item><title>Mirror: An LLM-powered programming-by-example programming language</title><link>https://austinhenley.com/blog/mirrorlang.html</link><description>&lt;a href="https://austinhenley.com/blog/mirrorlang.html"&gt;https://austinhenley.com/blog/mirrorlang.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Sun, 17 Nov 2024 20:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/mirrorlang.html</guid></item><item><title>How to setup self hosted wiki for your startup</title><link>https://themythicalengineer.com/how-to-setup-self-hosted-wiki-for-your-startup.html</link><description/><author>The Mythical Engineer</author><pubDate>Sun, 17 Nov 2024 07:22:00 GMT</pubDate><guid isPermaLink="true">https://themythicalengineer.com/how-to-setup-self-hosted-wiki-for-your-startup.html</guid></item><item><title>Too Stupid to Understand Git</title><link>https://studiofreya.org/reddit/2024-11-17-too-stupid-to-understand-git/</link><description>&lt;p&gt;From: &lt;a href="https://www.reddit.com/r/gamedev/comments/1gt7q7j/too_stupid_to_understand_git/"&gt;https://www.reddit.com/r/gamedev/comments/1gt7q7j/too_stupid_to_understand_git/&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Am I too stupid to understand Git? I&amp;rsquo;ve already watched a few tutorials on source tree, git desktop and github. But I still don&amp;rsquo;t understand the basics, which makes me feel quite alone with my limited mind. What is the difference between commit and push? Why do I even need them if I just want a backup? How does the twigs work? When I use git, I feel like I&amp;rsquo;m in a minefield. I press in fear that my voice will suddenly disappear because I&amp;rsquo;ve confused undoing commit with revert or pull or merge or whatever. Does anyone know of a foolproof tutorial that even idiots like me can use to understand this wise book?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h1 id="git---game"&gt;Git - game&lt;/h1&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th style="text-align: left;"&gt;Git command&lt;/th&gt;
          &lt;th style="text-align: left;"&gt;Game action&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td style="text-align: left;"&gt;commit&lt;/td&gt;
          &lt;td style="text-align: left;"&gt;save game&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style="text-align: left;"&gt;push&lt;/td&gt;
          &lt;td style="text-align: left;"&gt;upload a cloud save&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style="text-align: left;"&gt;pull&lt;/td&gt;
          &lt;td style="text-align: left;"&gt;load a cloud save&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style="text-align: left;"&gt;fetch&lt;/td&gt;
          &lt;td style="text-align: left;"&gt;get a cloud save without loading it&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style="text-align: left;"&gt;restore&lt;/td&gt;
          &lt;td style="text-align: left;"&gt;load the last save&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td style="text-align: left;"&gt;clone&lt;/td&gt;
          &lt;td style="text-align: left;"&gt;install&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;</description><author>Studiofreya SSG Site</author><pubDate>Sun, 17 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/reddit/2024-11-17-too-stupid-to-understand-git/</guid></item><item><title>2024-11-17/01</title><link>https://ho.dges.online/pictures/2024-11-17-01/</link><description/><author>ho.dges.online</author><pubDate>Sun, 17 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-11-17-01/</guid></item><item><title>2024-11-17/02</title><link>https://ho.dges.online/pictures/2024-11-17-02/</link><description/><author>ho.dges.online</author><pubDate>Sun, 17 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-11-17-02/</guid></item><item><title>/junk</title><link>https://taylor.town/junk</link><description>welcome to my internet yardsale</description><author>taylor.town</author><pubDate>Sun, 17 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/junk</guid></item><item><title>ChatGPT is Slipping</title><link>https://adriano.fyi/posts/chatgpt-is-slipping/</link><description>&lt;h2 id="chatgpt-is-slipping"&gt;ChatGPT is slipping&lt;/h2&gt;
&lt;p&gt;Three months ago I put code in production that utilizes &lt;code&gt;gpt-4o&lt;/code&gt; and/or &lt;code&gt;gpt-4o-mini&lt;/code&gt; models to analyze feedback about businesses and categorize it. The prompts instruct the models to identify categories of feedback, and in a second phase, extract some examples of what people said. This is a simplification, but it took very little effort to craft some prompts that enabled even the meager &lt;code&gt;gpt-4o-mini&lt;/code&gt; model to do exactly that. It didn&amp;rsquo;t feel like a stretch to imagine that this use case was well within ChatGPT&amp;rsquo;s limits based on the minimal effort put into a working solution. The results were genuinely useful, and the effort was low. It seemed like an obvious win.&lt;/p&gt;
&lt;p&gt;The code ran every two weeks, and the models had done an admirable job every time. Before putting results in front of users, I let the code run silently, only visible to a select few within our company. Very subjectively, we were satisfied with ChatGPT&amp;rsquo;s results and figured it was time to put them in front of users. So I threw some basic integration tests around the feature, gave it the old &lt;code&gt;go test -count 1000 ...&lt;/code&gt;, and released it to the world.&lt;/p&gt;
&lt;p&gt;Like most software engineers, I&amp;rsquo;m all too familiar with flaky tests, and was hesitant that tests around LLM output would be predictable enough to remain unflaky. But, running a test suite with &lt;code&gt;go test -count 1000&lt;/code&gt; without failure is pretty confidence inspiring. And it turns out that my hesitation was not terribly well-founded. The tests simply never failed for three months. I was so skeptical that a few times over the last month, I cracked the tests open and made sure we weren&amp;rsquo;t simply getting false negatives. We weren&amp;rsquo;t, and tests were running multiple times a day on local development machines and during both &lt;code&gt;staging&lt;/code&gt; and &lt;code&gt;production&lt;/code&gt; deployments. The tests were running about &lt;code&gt;50&lt;/code&gt; times per week and remained rock solid.&lt;/p&gt;
&lt;p&gt;That is, until &lt;code&gt;11/13/2024&lt;/code&gt;. The conversation in my head following that test failure went something like this&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I KNEW IT. The test has gone flaky; LLMs obviously can&amp;rsquo;t be treated like deterministic machines, this was &lt;em&gt;bound&lt;/em&gt; to go flaky. I&amp;rsquo;ll treat it like any other non-deterministic thing and just make the test easier to pass for now until I can re-focus on crafting better prompts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I guess you could say that in some way I was right. LLMs don&amp;rsquo;t have a well understood place in deterministic settings. But I was definitely wrong about another: that making the test easier to pass would let me kick this thing under the rug and forget about it for a while.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;11/15&lt;/code&gt; rolled around, and this feature failed so spectacularly that I&amp;rsquo;m still blushing with embarrassment from what it put in front of users. Luckily, it failed so spectacularly that very few of them saw anything at all because nearly every prompt failed to produce results. In my test environment, the models were presented with a relatively small amount of mock data. In production, the models were presented with far more data, and as it turns out, they perform much worse when presented with &lt;em&gt;more&lt;/em&gt; data.&lt;/p&gt;
&lt;p&gt;What should have happened on &lt;code&gt;11/13&lt;/code&gt; is I should have gone back to my original, more rigorous test (&lt;code&gt;go test -count 1000 ...&lt;/code&gt;) and checked whether ChatGPT itself had regressed. Had I done so on &lt;code&gt;11/13&lt;/code&gt;, the embarrassment of &lt;code&gt;11/15&lt;/code&gt; would have never occurred. Because I would have seen the tests fail &lt;em&gt;all 1000 times&lt;/em&gt;. In other words it became a deterministic failure.&lt;/p&gt;
&lt;p&gt;This isn&amp;rsquo;t your average regression. And I don&amp;rsquo;t intend to imply that ChatGPT is failing catastrophically for many users, because I&amp;rsquo;ve yet to find anyone else talking about similar regressions. But I&amp;rsquo;m certain that something fundamental changed in &lt;code&gt;gpt-4o&lt;/code&gt; on Wednesday, &lt;code&gt;11/13/2024&lt;/code&gt;. Maybe it&amp;rsquo;s a single parameter or weight that affects only a subset of users. Maybe that parameter or weight, or whatever it is affects only me. But what is certain is that &lt;em&gt;something&lt;/em&gt; changed, and it changed without any fanfare, like an announcement, blog post, or tweet.&lt;/p&gt;
&lt;p&gt;Take this post as a reminder that these models can change on a whim, despite what is published at &lt;a href="https://platform.openai.com/docs/models"&gt;https://platform.openai.com/docs/models&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;ChatGPT may not be slipping for everyone, but it&amp;rsquo;s certainly slipping for me.&lt;/p&gt;
&lt;h2 id="so-whats-next"&gt;So what&amp;rsquo;s next?&lt;/h2&gt;
&lt;p&gt;The first thing I did was disable the feature. I&amp;rsquo;ll begin testing other models from other providers, but ultimately, I think this should be the beginning of a conversation about self-hosting and local LLMs. I can&amp;rsquo;t with good conscience continue building on a platform that can change on a whim without any public acknowledgement of what is changing.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s already a slow movement toward bringing LLM functionality in-house rather than relying on yet-another 3rd party vendor like OpenAI, Google, or Anthropic. A few things that have my wheels turning are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Langchaingo (&lt;a href="https://github.com/tmc/langchaingo"&gt;https://github.com/tmc/langchaingo&lt;/a&gt;)&lt;/strong&gt;: Abstract away the providers with a common interface. This is not to say the providers and models are fungible; they&amp;rsquo;re not. But langchaingo makes dropping different models into applications pretty seamless.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Go blog&amp;rsquo;s write up (&lt;a href="https://go.dev/blog/llmpowered"&gt;https://go.dev/blog/llmpowered&lt;/a&gt;)&lt;/strong&gt;: This was a great overview of how one might begin the process of making more LLM functionality local&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ollama (&lt;a href="https://ollama.com/"&gt;https://ollama.com/&lt;/a&gt;)&lt;/strong&gt;: Local-first LLMs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ML in Go with a Python sidecar (&lt;a href="https://eli.thegreenplace.net/2024/ml-in-go-with-a-python-sidecar/"&gt;https://eli.thegreenplace.net/2024/ml-in-go-with-a-python-sidecar/&lt;/a&gt;)&lt;/strong&gt;: The title says it all. I&amp;rsquo;m not in love with &amp;ldquo;sidecar&amp;rdquo; architecture, but it&amp;rsquo;s sensible in some settings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have you seen any similar declines in ChatGPT quality? I&amp;rsquo;d love to hear from anyone else: &lt;code&gt;chatgptisslipping@adriano.fyi&lt;/code&gt; or through the Mastodon comments below.&lt;/p&gt;
&lt;p&gt;Cheers&lt;/p&gt;
&lt;h2 id="updates"&gt;Updates&lt;/h2&gt;
&lt;h3 id="11182024"&gt;11/18/2024&lt;/h3&gt;
&lt;p&gt;I decided to go back to the original test today and re-run it. It passed on the first try, and every subsequent try (it&amp;rsquo;s passed dozens of times now). So what was a 100% failure rate days ago, is once again a 100% success rate. It seems like OpenAI is comfortable taking API users for a ride. I assumed models would fluctuate regularly on the web frontend, and naively thought the API would have more stability. Learn from my naivety.&lt;/p&gt;</description><author>Adriano Caloiaro's personal blog</author><pubDate>Sun, 17 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://adriano.fyi/posts/chatgpt-is-slipping/</guid></item><item><title>My 2024 New Mac Setup</title><link>https://www.swyx.io/new-mac-setup-2024</link><description>&lt;p&gt;I set up a new Mac for work today. Here's everything I use on a Mac for fullstack web development.&lt;/p&gt;</description><author>swyx's site RSS Feed</author><pubDate>Sat, 16 Nov 2024 19:43:47 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/new-mac-setup-2024</guid></item><item><title>Conversation Branching</title><link>https://www.danielcorin.com/posts/2024/conversation-branching/</link><description>Conversation Branching</description><author>Thought Eddies</author><pubDate>Sat, 16 Nov 2024 19:02:42 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2024/conversation-branching/</guid></item><item><title>Building OpenJDK From Source On macOS</title><link>https://www.morling.dev/blog/building-openjdk-from-source-on-macos/</link><description>&lt;div class="paragraph"&gt;
&lt;p&gt;Every now and then, it can come in very handy to build OpenJDK from source yourself,
for instance if you want to explore a feature which is under development on a branch for which no builds are published.
For some reason I always thought that building OpenJDK is a very complex processing,
requiring the installation of arcane tool chains etc.
But as it turns out, this actually not true:
the project does a great job of documenting what’s needed and only a few steps are necessary to build your very own JDK.&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Sat, 16 Nov 2024 16:25:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/building-openjdk-from-source-on-macos/</guid></item><item><title>Finding better domain names</title><link>https://ilearnt.com/blog/findingdomainnames/</link><description>&lt;p&gt;Finding a good available domain name for a project, product or company is hard but there is one web tool that I have found is more successful than others.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Sat, 16 Nov 2024 14:15:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/findingdomainnames/</guid></item><item><title>From Corniolo to Passo Braccina via Valpisella</title><link>https://nicolaiarocci.com/from-corniolo-to-passo-braccina-via-valpisella/</link><description>&lt;p&gt;I took a daily hike in my beloved Apennine Mountains a few weeks ago. One of my favourite motorcycling routes is the narrow, engaging, panoramic road that unites Corniolo in the Bidente Valley with Marradi and the Mugello area via the Braccina Pass. I always wanted to return and hike through it; the moment had come.&lt;/p&gt;
&lt;p&gt;It was an excellent circular tour that, to the merits of moderation-it is not too long, nor too strenuous, the ascent is always gradual, etc. - also combines the variety of the environment, shading from mixed mid-altitude broadleaf forests (hornbeams, oaks, ash, cherry, maple) to coniferous reforestations that in this case, with increasing age and size, have taken on a certain dignity.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Sat, 16 Nov 2024 10:06:23 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/from-corniolo-to-passo-braccina-via-valpisella/</guid></item><item><title>Imposter Syndrome</title><link>http://blog.jonandnic.com/2024/11/15/imposter-syndrome/</link><description>In my son's experience with a bad English teacher I saw my own struggles with academics. I also had some really good teachers, and a few really bad ones. The good ones would say things like "he's not living up to his potential." The bad ones told me I was stupid. Its funny how both of those things stick with you the rest of your life...</description><author>jonandnic dot com</author><pubDate>Sat, 16 Nov 2024 05:04:41 GMT</pubDate><guid isPermaLink="true">http://blog.jonandnic.com/2024/11/15/imposter-syndrome/</guid></item><item><title>Fortunate: A Modern Motivational App for Linux</title><link>https://thoughts.greyh.at/posts/fortunate/</link><description>Recently, I found myself searching for a polished motivational app for Linux, but everything I discovered either required using the terminal or had interfaces that felt outdated. Knowing the wealth of wisdom available in fortune-mod&amp;rsquo;s databases, I decided to create Fortunate, a modern graphical interface that delivers inspiring quotes throughout your day, while giving me an opportunity to explore GUI development with Fyne.</description><author>Terminal Thoughts</author><pubDate>Sat, 16 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://thoughts.greyh.at/posts/fortunate/</guid></item><item><title>Chatbots Decoded: Exploring AI (Review)</title><link>https://ztoz.blog/posts/chatbots-decoded/</link><description>&lt;p&gt;&lt;em&gt;Chatbots Decoded: Exploring AI&lt;/em&gt; is a &lt;a href="https://computerhistory.org/exhibits/chatbots-decoded/"&gt;new exhibit&lt;/a&gt; from the Computer History Museum (CHM) opening November 20th, 2024. In development for more than a year, the exhibit covers both the history of chatbots and conversational interfaces as well as the social ramifications of the technology. While there is not an explanation for why GPUs rather than CPUs are so useful for learning or what &amp;ldquo;attention is all you need&amp;rdquo; means, both are alluded too and I think, many audiences, including children, will enjoy it.&lt;/p&gt;
&lt;p&gt;Conversational interfaces, as typified by chatbots, have had a long parallel development in computing with more traditional interfaces. Science fiction has featured robots and automated creatures controlled by voice or written words as far back as the Golem and R.U.R. &lt;em&gt;Star Trek&lt;/em&gt;&amp;rsquo;s main computer presents a mixed interface, both tactile graphical displays and voice control. The series differentiated between the computer&amp;rsquo;s intelligence and sentience. Although the computer could flexibly carry out tasks, it lacked any introspection, motivations, or free will. This distinction is one of the subtleties explored in the exhibit.&lt;/p&gt;
&lt;p&gt;The exhibit begins with fictional robots that, by obeying voice commands, helped shape both our expectations as well as fears. The first real grounded example is &lt;a href="https://doi.org/10.1145/365153.365168"&gt;ELIZA&lt;/a&gt;, the conversational system which was typically used to imitate a Rogerian therapist. Visitors have access to a terminal where they can interact with an implementation, hopefully experiencing some of the magic feeling of interactivity but also witnessing the limitations of the technology.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="museum panel describing ELIZA with computer terminal" src="eliza-panel.jpg" width="100%" /&gt;&lt;figcaption&gt;
            &lt;p&gt;ELIZA, the earliest chatbot system&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;figure&gt;&lt;img alt="interactions with ELIZA" src="eliza-interface.jpg" width="100%" /&gt;&lt;figcaption&gt;
            &lt;p&gt;Interact with an ELIZA acting as a Rogerian therapist&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Developments in symbolic logic and improvements such as A.L.I.C.E. are recounted. However, the next big revolution is deep learning where a timeline describes the exponential growth in the models, the amount of data, and the amount of funding. This leads into probably the central showcase: the Ameca exhibit.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="ameca's robotic human face" src="ameca.jpg" width="100%" /&gt;&lt;figcaption&gt;
            &lt;p&gt;Ameca as a chatbot with a physical face&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;&lt;a href="https://engineeredarts.co.uk/robot/ameca/"&gt;Ameca&lt;/a&gt; is a high-fidelity robotic face joined to a ChatGPT model. Visitors can step right up and start talking to Ameca, which tries to spark some conversation on its own. According to a staff member, children feel an immediate emotional connection to the robot, laughing and giggling. For my part, I felt self-conscious, wanting to say something profound amongst all the humans in ear-shot. In order to prompt conversation, Ameca will urge visitors on. This can end up interrupting the visitor&amp;rsquo;s attempts at interaction, but that ends up increasing the humanity of the experience.&lt;/p&gt;
&lt;p&gt;As a topic that is more current event than historical, &lt;em&gt;Chatbots Decoded&lt;/em&gt; concludes with a large section of open questions. For instance, CheatGPT raises both the potential of LLMs as education aids, the concerns of students using LLMs as labor-saving devices, the battle to detect LLM generated text versus student authored text, and how relevant the older debate about student use of calculators is to the current fight. With no planned closing date for the exhibit, CHM will need to gauge when new developments warrant an update to the display.&lt;/p&gt;
&lt;figure&gt;&lt;img alt="museum panel for cheatgpt" src="cheatgpt.jpg" width="100%" /&gt;&lt;figcaption&gt;
            &lt;p&gt;Should students use ChatGPT?&lt;/p&gt;
        &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;All photos copyright of the author.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: As a Supporting Member of the CHM, I gained early access to the exhibit. I was not compensated for the review.&lt;/p&gt;
&lt;/blockquote&gt;</description><author>ℤ→ℤ</author><pubDate>Fri, 15 Nov 2024 21:20:34 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/chatbots-decoded/</guid></item><item><title>Add Alt Text to an Image with an LLM and Cursor</title><link>https://www.danielcorin.com/til/cursor/add-alt-to-an-image/</link><description>Add Alt Text to an Image with an LLM and Cursor</description><author>Thought Eddies</author><pubDate>Fri, 15 Nov 2024 17:35:21 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/cursor/add-alt-to-an-image/</guid></item><item><title>Thinking differently can be worth a lot</title><link>https://ilearnt.com/blog/warpstream/</link><description>&lt;p&gt;We use the technology Apache Kafka as a key element in the implementation of the distributed ledger we have developed.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Fri, 15 Nov 2024 09:12:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/warpstream/</guid></item><item><title>Tailwind... quick thought</title><link>https://robkohr.com/articles/tailwind----quick-thought</link><description>Tailwind... quick thought</description><author>RobKohr's Blog</author><pubDate>Fri, 15 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/tailwind----quick-thought</guid></item><item><title>I Feel Unsafe</title><link>https://jezenthomas.com/2024/11/I-feel-unsafe/</link><description>&lt;p&gt;I awoke to the sound of a violent explosion earlier than I had wanted that
morning. The walls, windows, and furniture vibrated. A cruise missile had
struck a warehouse owned by a national supermarket chain, roughly a kilometre
from the hotel I was staying in. The russians had fired it – targeting civilian
infrastructure as they regularly do – perhaps a dozen minutes earlier. It was
June 24th, 2024. The time was 07:03.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Plume of smoke from russian cruise missile attack on civilian infrastructure." src="/static/img/i-feel-unsafe/tavriya-v.jpg" /&gt;
&lt;figcaption&gt;
Plume of smoke from a russian cruise missile hitting a supermarket warehouse,
shot from the window of my hotel room in Odesa, Ukraine.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;This was hardly my first rodeo. Cruise missiles regularly flew over the
apartment I was renting in Odesa. The local air defence would light up the sky
when attacks came at night; as they often did. One attack in 2022 saw an
apartment building struck a few streets behind mine, killing a young woman and
her two children. Her husband wasn’t in the building at the time, and
distraught by the slaughter of his entire family, he joined his country’s
defence on the front line. Ultimately, he died too.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Smoke trail from an air defence missile after destroying an incoming Shahed drone." src="/static/img/i-feel-unsafe/fontanska.jpg" /&gt;
&lt;figcaption&gt;
Trail from an air defence missile, and the smoke from a successfully
destroyed Shahed drone, shot from the kitchen window of my rental apartment
in Odesa, October 2022.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;With my British passport, I have the luxury of leaving any time living in an
active war zone begins to take its toll. My closest friends however, enjoy no
such luxury. The russians came to rape, torture, and kill. Neither women nor
children are spared. Countless invaders have admitted to the brutality, at
times recounting that &lt;a href="https://www.politico.eu/article/former-wagner-group-commanders-azmat-uldarov-alexey-savichev-confess-murder-ukraine-civilians-including-children/"&gt;even small children are executed&lt;/a&gt; with a bullet in
the brain at point blank range. The Ukrainians have no choice but to stay and
fight.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I attended NixCon a few weeks ago in Berlin. Overall I enjoyed the experience;
it feels a little less like a conference, and a little more like a get together
for the relatively small NixOS community. About half way through the
conference, the organisers announced that one attendee had been asked to leave.
They were alleged to have made another attendee “feel unsafe”.&lt;/p&gt;
&lt;p&gt;I don’t know specifically what happened, beyond someone being made to “feel
unsafe”, nor do I have any reason to doubt the accusation (and I’m glad the
moderators took the complaint seriously and acted upon it).&lt;/p&gt;
&lt;p&gt;What sticks in my mind is the phrase “feel unsafe”. In my experience, I’ve only
ever heard this phrase said by people who hold political views radically more
left-wing than my own (and for the avoidance of doubt, I’m a bit of a leftie).
NixCon – and perhaps the NixOS community more broadly – appears to have an
atypically high concentration of people who would describe themselves as
Marxists, or Communists, or Anarchists, &lt;em&gt;etc&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Indeed, walking around the conference venue, I spotted at least two dozen Nix
hackers with radical left-wing political symbols and slogans emblazoned across
their laptops. In the heart of Berlin, the hammer and sickle is apparently fine
and reasonable.&lt;/p&gt;
&lt;p&gt;My heritage is Polish. I have good memory of my great-grandmother. Her husband,
I never met. He died as a slave in a Nazi concentration camp. That side of my
family has always lived on the West side of Poland. If they had lived as far
East as they do West, then no doubt my great-grandfather would have died at the
hands of the Soviets. The Nazis and the Soviets were allied at the start of The
Second World War, so to me — and to millions of other Poles no doubt — the
hammer and sickle and the swastika are essentially interchangeable.&lt;/p&gt;
&lt;p&gt;And the discomfort I experience seeing these political symbols and slogans is not
so abstract. The commonly held position among people who describe themselves as
Communist today is that Ukraine should not be given the lethal aid they need
to defend themselves from a genocide that, to date, by some estimates has seen
about a million casualties.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Campaign material from a Danish revolutionary communist organisation saying 'Books, not Bombs'." src="/static/img/i-feel-unsafe/books-not-bombs.jpg" /&gt;
&lt;figcaption&gt;
Campaign material from the ‘Revolutionary Communist Party’ in Denmark, who are &lt;a href="https://marxist.dk/boeger-ikke-bomber/"&gt;explicitly against arming Ukraine&lt;/a&gt;.
&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The reasoning I have been able to discern for this position is:&lt;/p&gt;
&lt;ol type="1"&gt;
&lt;li&gt;Guns and bombs are bad and kill people, so the West shouldn’t make them or
send them to Ukraine.&lt;/li&gt;
&lt;li&gt;Naziism is bad, and the Communists defeated the Nazis, therefore Communism
is good (conveniently forgetting the Molotov-Ribbentrop Pact).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The reason why my closest friends in Odesa are not dead today is because
hundreds of thousands of brave Ukrainian men and women have been successfully
defending their country from terrorists and barbarians with the use of Western
weapons.&lt;/p&gt;
&lt;p&gt;To say that Ukraine should not be given weapons is tantamount to saying that
all of my friends should be dead. And, I suppose, that I should be dead too.
It’s only blind luck that neither cruise missiles nor Shahed drones have
struck a building that I was staying in. Although come to think of it, a
Shahed drone did indeed hit a building where I used to live. Possibly even the
same floor of that building. There have been so many attacks that I don’t
perfectly recall.&lt;/p&gt;
&lt;p&gt;And yet, I’ll bet that if I were to use the same phrase at that conference — if
I were to say that I “feel unsafe” — I somehow doubt that my complaint would be
taken seriously. I am — superficially anyway — not a part of the persecuted
class, so violence against me is fine.&lt;/p&gt;</description><author>jezenthomas.com</author><pubDate>Fri, 15 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jezenthomas.com/2024/11/I-feel-unsafe/</guid></item><item><title>Linux Assembly Part 4: Arithmetic Operations</title><link>https://cookie.engineer/weblog/articles/linux-assembly-part-4-arithmetic-operations.html</link><description>Learn Linux Assembly to do arithmetic operations.</description><author>Cookie Engineer's Web Log</author><pubDate>Fri, 15 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://cookie.engineer/weblog/articles/linux-assembly-part-4-arithmetic-operations.html</guid></item><item><title>Vanishingly Few Developers Can Build an Entire App From Start to Finish — Are You One of Them?</title><link>https://chrisfrew.in/blog/vanishingly-few-developers-can-build-an-app-from-scratch/</link><description>Yep, I'm dropping truth bombs!</description><author>Chris' Full Stack Blog RSS Feed</author><pubDate>Fri, 15 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://chrisfrew.in/blog/vanishingly-few-developers-can-build-an-app-from-scratch/</guid></item><item><title>Bored in an exam</title><link>https://siddhesh.substack.com/p/bored-in-an-exam</link><description>A poem</description><author>Obvious Bicycle</author><pubDate>Thu, 14 Nov 2024 23:59:24 GMT</pubDate><guid isPermaLink="true">https://siddhesh.substack.com/p/bored-in-an-exam</guid></item><item><title>Small things can make a difference</title><link>https://ilearnt.com/blog/smallthings/</link><description>&lt;p&gt;A single, creative decision transformed our interactions with some junior engineers, boosting their morale and integrating them seamlessly into our team.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Thu, 14 Nov 2024 19:05:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/smallthings/</guid></item><item><title>Practical Deep Learning, Lesson 5, Pricing Iowa Houses with Random Forests</title><link>https://www.danielcorin.com/til/fastai/lesson5-iowa-housing-prices/</link><description>Practical Deep Learning, Lesson 5, Pricing Iowa Houses with Random Forests</description><author>Thought Eddies</author><pubDate>Thu, 14 Nov 2024 15:27:45 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/fastai/lesson5-iowa-housing-prices/</guid></item><item><title>使用 Go 开发 AI Agent的选择：Genkit for Go</title><link>https://www.4async.com/2024/11/building-ai-agent-with-genkit-for-go/</link><description>&lt;img alt="Featured image of post 使用 Go 开发 AI Agent的选择：Genkit for Go" src="https://www.4async.com/2024/11/building-ai-agent-with-genkit-for-go/cover.png" /&gt;&lt;h2 id="什么是-genkit"&gt;什么是 Genkit
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://github.com/firebase/genkit" rel="noopener" target="_blank"&gt;Genkit&lt;/a&gt; 是一个 Google Firebase 团队开发的 AI Agent 开发框架，用于构建现代、高效的 AI 应用。它目前包含一个 &lt;a class="link" href="https://firebase.google.com/docs/genkit?hl=zh-cn" rel="noopener" target="_blank"&gt;Node.js 的实现&lt;/a&gt; 和一个 &lt;a class="link" href="https://firebase.google.com/docs/genkit-go/get-started-go" rel="noopener" target="_blank"&gt;Go 语言的实现&lt;/a&gt;。之所以注意到这个框架是因为 Go 团队在他们的&lt;a class="link" href="https://go.dev/blog/15years" rel="noopener" target="_blank"&gt;十五周年博客&lt;/a&gt;中提到了它。Go 团队在博客中提到，他们正在努力使 Go 成为构建生产 AI 系统的优秀语言，并且他们正在为流行的 Go 语言框架提供支持，包括 &lt;a class="link" href="https://github.com/tmc/langchaingo" rel="noopener" target="_blank"&gt;LangChainGo&lt;/a&gt; 和 &lt;a class="link" href="https://developers.googleblog.com/en/introducing-genkit-for-go-build-scalable-ai-powered-apps-in-go/" rel="noopener" target="_blank"&gt;Genkit&lt;/a&gt;。如果你了解过 AI 开发，那么 &lt;a class="link" href="https://www.langchain.com/" rel="noopener" target="_blank"&gt;LangChain&lt;/a&gt; 一定并不陌生。LangChainGo 就是 LangChain 的 Go 语言实现。但是对应的，我们还需要一个单独的 AI Agent 开发框架，帮助我们组织 AI Agent 的开发。LangChain 的生态位中，对应的是 &lt;a class="link" href="https://www.langchain.com/langgraph" rel="noopener" target="_blank"&gt;LangGraph&lt;/a&gt;，在 Go 语言的生态位中，你可以选择 Genkit 或者 &lt;a class="link" href="https://github.com/tmc/langgraphgo" rel="noopener" target="_blank"&gt;LangGraphGo&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;但是，值得注意的是，目前 Genkit 仍旧是在 Alpha 的状态，所以，&lt;strong&gt;如果你希望将 Genkit 用于生产环境，在 2024 年这个时间点请注意未来可能的 API 变动。&lt;/strong&gt; 目前 Genkit 官方支持的模型功能仍旧是以 Google 家的产品为主，如果你需要使用其他 AI 模型，你可能需要暂时先使用第三方 plugin（比如 OpenAI）。&lt;/p&gt;</description><author>ipfans's Blog</author><pubDate>Thu, 14 Nov 2024 13:28:00 GMT</pubDate><guid isPermaLink="true">https://www.4async.com/2024/11/building-ai-agent-with-genkit-for-go/</guid></item><item><title>Film Development Calculator</title><link>https://lagomor.ph/projects/filmdevelopment/</link><description>&lt;div class="calculator-container"&gt;
 &lt;div class="calc-columns"&gt;
 &lt;div class="calc-column"&gt;
 &lt;div class="input-section"&gt;
 &lt;h3&gt;Film &amp; Developer&lt;/h3&gt;
 &lt;div class="input-group"&gt;
 &lt;label for="filmStock"&gt;Film Stock:&lt;/label&gt;
 &lt;select id="filmStock"&gt;
 &lt;option value="custom"&gt;Custom&lt;/option&gt;
 &lt;option value="tri-x-400"&gt;Tri-X 400&lt;/option&gt;
 &lt;option selected="selected" value="hp5-400"&gt;HP5+ 400&lt;/option&gt;
 &lt;option value="fp4-125"&gt;FP4+ 125&lt;/option&gt;
 &lt;option value="tmax-400"&gt;T-Max 400&lt;/option&gt;
 &lt;option value="delta-400"&gt;Delta 400&lt;/option&gt;
 &lt;option value="pan-f-50"&gt;Pan F+ 50&lt;/option&gt;
 &lt;option value="apx-100"&gt;APX 100&lt;/option&gt;
 &lt;option value="fomapan-400"&gt;Fomapan 400&lt;/option&gt;
 &lt;/select&gt;
 &lt;/div&gt;

 &lt;div class="input-group"&gt;
 &lt;label for="developer"&gt;Developer:&lt;/label&gt;
 &lt;select id="developer"&gt;
 &lt;option value="custom"&gt;Custom&lt;/option&gt;
 &lt;option value="rodinal-1:25"&gt;Rodinal 1:25&lt;/option&gt;
 &lt;option selected="selected" value="rodinal-1:50"&gt;Rodinal 1:50&lt;/option&gt;
 &lt;option value="rodinal-1:100"&gt;Rodinal 1:100 (Stand)&lt;/option&gt;
 &lt;option value="d76-stock"&gt;D-76 Stock&lt;/option&gt;
 &lt;option value="d76-1:1"&gt;D-76 1:1&lt;/option&gt;
 &lt;option value="hc110-b"&gt;HC-110 Dilution B&lt;/option&gt;
 &lt;option value="xtol-stock"&gt;XTOL Stock&lt;/option&gt;
 &lt;option value="xtol-1:1"&gt;XTOL 1:1&lt;/option&gt;
 &lt;option value="ddx-1:4"&gt;DD-X 1:4&lt;/option&gt;
 &lt;/select&gt;
 &lt;/div&gt;
 &lt;div class="preset-note" id="presetNote"&gt;&lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="input-section"&gt;
 &lt;h3&gt;Setup Parameters&lt;/h3&gt;
 &lt;div class="input-group"&gt;
 &lt;label for="volume"&gt;Tank Volume (ml):&lt;/label&gt;
 &lt;input id="volume" max="5000" min="100" step="5" type="number" value="375" /&gt;
 &lt;/div&gt;

 &lt;div class="input-group"&gt;
 &lt;label for="ratio"&gt;Developer Ratio (1:x):&lt;/label&gt;
 &lt;div class="input-row"&gt;
 &lt;input id="ratio" max="200" min="1" type="number" value="50" /&gt;
 &lt;span class="note"&gt;e.g., 50 for 1:50&lt;/span&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="input-group"&gt;
 &lt;label for="baseTime"&gt;Base Development Time (minutes):&lt;/label&gt;
 &lt;input id="baseTime" max="120" min="1" step="0.5" type="number" value="11" /&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="calc-column"&gt;
 &lt;div class="result"&gt;
 &lt;h3&gt;Dilution Results&lt;/h3&gt;
 &lt;div class="result-row"&gt;
 &lt;span&gt;Developer needed:&lt;/span&gt;
 &lt;span&gt;&lt;span id="developerAmount"&gt;7.5&lt;/span&gt; ml&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="result-row"&gt;
 &lt;span&gt;Water needed:&lt;/span&gt;
 &lt;span&gt;&lt;span id="waterAmount"&gt;367.5&lt;/span&gt; ml&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="result-row"&gt;
 &lt;span&gt;Adjusted development time:&lt;/span&gt;
 &lt;span&gt;&lt;span id="adjustedTime"&gt;11&lt;/span&gt; minutes&lt;/span&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="process-steps"&gt;
 &lt;h3&gt;Process Timeline&lt;/h3&gt;
 &lt;div class="step"&gt;
 &lt;span&gt;Pre-soak:&lt;/span&gt;
 &lt;span id="presoak-display"&gt;1:00&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="step"&gt;
 &lt;span&gt;Development:&lt;/span&gt;
 &lt;span id="developmentStep"&gt;11:00&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="step"&gt;
 &lt;span&gt;Stop Bath:&lt;/span&gt;
 &lt;span id="stopbath-display"&gt;1:00&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="step"&gt;
 &lt;span&gt;Fix:&lt;/span&gt;
 &lt;span id="fix-display"&gt;5:00&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="step"&gt;
 &lt;span&gt;Rinse:&lt;/span&gt;
 &lt;span id="rinse-display"&gt;10:00&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="step"&gt;
 &lt;strong&gt;Total Time:&lt;/strong&gt;
 &lt;strong id="totalTime"&gt;28:00&lt;/strong&gt;
 &lt;/div&gt;

 &lt;button class="settings-toggle" id="toggleAdvanced" type="button"&gt;Customize Step Times&lt;/button&gt;
 &lt;div class="advanced-settings" id="advancedSettings"&gt;
 &lt;div class="step-times-grid"&gt;
 &lt;div class="step-time-input"&gt;
 &lt;label for="presoak"&gt;Pre-soak (min):&lt;/label&gt;
 &lt;input id="presoak" max="10" min="0" step="0.5" type="number" value="1" /&gt;
 &lt;/div&gt;
 &lt;div class="step-time-input"&gt;
 &lt;label for="stopbath"&gt;Stop Bath (min):&lt;/label&gt;
 &lt;input id="stopbath" max="5" min="0.5" step="0.5" type="number" value="1" /&gt;
 &lt;/div&gt;
 &lt;div class="step-time-input"&gt;
 &lt;label for="fix"&gt;Fix (min):&lt;/label&gt;
 &lt;input id="fix" max="15" min="2" step="0.5" type="number" value="5" /&gt;
 &lt;/div&gt;
 &lt;div class="step-time-input"&gt;
 &lt;label for="rinse"&gt;Rinse (min):&lt;/label&gt;
 &lt;input id="rinse" max="30" min="5" step="1" type="number" value="10" /&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="input-section adjustments-full"&gt;
 &lt;h3&gt;Adjustments&lt;/h3&gt;
 &lt;div class="adjustments-grid"&gt;
 &lt;div class="input-group"&gt;
 &lt;label for="temperature"&gt;
 Temperature:
 &lt;span class="unit-toggle"&gt;
 &lt;button class="active" id="unitC" type="button"&gt;°C&lt;/button&gt;
 &lt;button id="unitF" type="button"&gt;°F&lt;/button&gt;
 &lt;/span&gt;
 &lt;/label&gt;
 &lt;div class="slider-container"&gt;
 &lt;input id="temperature" max="24" min="18" step="0.5" type="range" value="20" /&gt;
 &lt;span id="tempValue"&gt;20°C&lt;/span&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="input-group"&gt;
 &lt;label for="pushPull"&gt;Push/Pull Adjustment:&lt;/label&gt;
 &lt;div class="slider-container"&gt;
 &lt;input id="pushPull" max="4" min="-4" type="range" value="0" /&gt;
 &lt;span id="pushValue"&gt;0 stops&lt;/span&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="input-group"&gt;
 &lt;label for="agitationInterval"&gt;Agitation Reminder:&lt;/label&gt;
 &lt;select id="agitationInterval"&gt;
 &lt;option value="0"&gt;Off&lt;/option&gt;
 &lt;option value="30"&gt;Every 30 seconds&lt;/option&gt;
 &lt;option selected="selected" value="60"&gt;Every 60 seconds&lt;/option&gt;
 &lt;option value="120"&gt;Every 2 minutes&lt;/option&gt;
 &lt;/select&gt;
 &lt;/div&gt;
 &lt;/div&gt;
 &lt;/div&gt;

 &lt;div class="toolbar"&gt;
 &lt;button id="copySettings" type="button"&gt;Copy Settings&lt;/button&gt;
 &lt;button id="resetDefaults" type="button"&gt;Reset to Defaults&lt;/button&gt;
 &lt;/div&gt;

 &lt;div class="timer-section"&gt;
 &lt;h3&gt;Development Timer&lt;/h3&gt;
 &lt;div class="agitation-alert" id="agitationAlert"&gt;&lt;/div&gt;
 &lt;div class="timer-display" id="timerDisplay"&gt;00:00&lt;/div&gt;
 &lt;div class="timer-progress" id="timerProgress"&gt;
 &lt;div class="timer-progress-fill" id="progressFill"&gt;&lt;/div&gt;
 &lt;div class="timer-progress-segments" id="progressSegments"&gt;&lt;/div&gt;
 &lt;/div&gt;
 &lt;div class="timer-step"&gt;
 Current step: &lt;span id="currentStep"&gt;Not started&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="timer-controls"&gt;
 &lt;button id="startTimer" type="button"&gt;Start&lt;/button&gt;
 &lt;button disabled="disabled" id="skipStep" type="button"&gt;Skip to Next&lt;/button&gt;
 &lt;button disabled="disabled" id="resetTimer" type="button"&gt;Reset&lt;/button&gt;
 &lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;



&lt;hr /&gt;
&lt;details class="custom-details"&gt;
Working Chemical Dilutions &amp; Stock Life
&lt;div class="details-content"&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th style="text-align: left;"&gt;Developer&lt;/th&gt;
 &lt;th style="text-align: left;"&gt;Working Dilution&lt;/th&gt;
 &lt;th style="text-align: left;"&gt;Stock Life&lt;/th&gt;
 &lt;th style="text-align: left;"&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;D-76&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;1:1&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;6 months&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Life improves in full bottles&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;HC-110&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Dilution B (1:31)&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;2+ years&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Syrupy concentrate very stable&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Dilution E (1:47)&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;2+ years&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;For lower contrast&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Dilution H (1:63)&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;2+ years&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;For stand development&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;XTOL&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Stock&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;2-3 months&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Sudden death - test before critical use&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;1:1&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;2-3 months&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;More economical&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;Rodinal&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;1:25&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Decades&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;High acutance, pronounced grain&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;1:50&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Decades&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Standard development&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;DD-X&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;1:4&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;6 months&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Professional choice, fine grain&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;Microphen&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Stock&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;6 months&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Best for push processing&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;1:1&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;6 months&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Normal development&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;Pyrocat-HD&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;1:1:100&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;1 year (A), 6 months (B)&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Excellent for alternative processes&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;ID-11&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Stock&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;6 months&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Similar to D-76&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;1:1&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;6 months&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Economy option&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;Perceptol&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Stock&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;4 months&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Ultra-fine grain&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td style="text-align: left;"&gt;&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;1:1&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;4 months&lt;/td&gt;
 &lt;td style="text-align: left;"&gt;Extended development&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Notes:&lt;/strong&gt;&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Thu, 14 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/projects/filmdevelopment/</guid></item><item><title>A Formative Artifact</title><link>https://www.mgaudet.ca/blog/2024/11/13/a-formative-artifact</link><description>&lt;p&gt;Do you have a formative artifact from your childhood? Maybe a TV show, a book, a game which you feel (perhaps wrongly) you can trace all your personality back to. &lt;/p&gt;&lt;p&gt;When I was a kid, probably 7-8 until I was probably 10 or 11, I had this book which I remembered as a book of facts. It wasn’t an encyclopedia, but it had this eclectic bent -- I mostly remember the science bits, but there was also bits about society and culture too.&lt;/p&gt;&lt;p&gt;It was reasonably large, with large illustrations and I read this book over and over and over and over until it fell apart. Literally. The last time I remember seeing it, I had read it until the covers had both fallen off, and all that remained was the spine. At some point presumably when I had outgrown it, we must have cleaned out my room and thrown it out. After all, in tatters it was scarcely better than trash. &lt;/p&gt;&lt;p&gt;Of course, looking back, this book was definitely one of my formative artifacts. What it instilled in me was a thirst for knowledge; eclectic wide ranging knowledge. Of course, the challenge being that like this book, my knowledge is patchy and broad. &lt;/p&gt;&lt;p&gt;Fast forward almost 25 years, and I found myself thinking about this book. Raising my daughter, and seeing the multitude of ways in which her childhood is different than mine, I keep wondering what will be her formative artifacts. What thing will captivate her? Could it be something like my book? Could I find that book perhaps? Would a book that old capture her imagination?&lt;/p&gt;&lt;p&gt;Of course, after so long, I could no longer remember what this book was called. Who wrote it. What the cover looked like. If I’m being honest, I could barely remember the actual contents of the book were; some illustrations burned deep into my brain mostly. &lt;/p&gt;&lt;p&gt;A few weeks ago I was thinking about it, and I realized I did know a few things about it. Maybe enough to find it? I remembered that it was published by Reader’s Digest. Given when I was reading it, probably in the 1990s. My recollection was that it was a “science” book. &lt;/p&gt;&lt;p&gt;So I started looking. I had been chatting with a friend about this book, and thought... this could well be just the kind of vague nonsense that ChatGPT might actually be helpful with. Not that I expected it to find the book, but that it might help me narrow down on something. &lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Me&lt;/strong&gt;: Looking for an old book published in Canada by Readers Digest. It was available in the nineties. It was a thick book filled with facts and experiments. Aimed at young kids. I don’t have a title.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ChatGPT&lt;/strong&gt;: Without a title, narrowing it down to a specific book can be tricky, but based on your description, it might be one of the popular educational reference books or experiment guides published by Reader's Digest during the 1990s. Reader's Digest released several children's educational and activity books during that time. Some titles that could fit your description are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;How Science Works (Reader's Digest, 1991): This book explained scientific concepts through facts and hands-on experiments, aimed at younger readers.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The How and Why Library (Reader's Digest): A multi-volume set filled with facts, trivia, and educational material aimed at children.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You might try searching through secondhand bookstores or websites like AbeBooks, which specialize in out-of-print books. If you're near a large library, they might have archives of Reader's Digest publications from that era as well.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;Doing some searches on “How Science Works” and the “How and Why Library”, I was able to quickly rule out the “How and Why Library”. But... “How Science Works”, well that fit the bill pretty good. So I found a copy on AbeBooks, and it was pretty affordable. I managed to find one photo of the interior... and I wasn’t sure. My memory was fuzzy, but I thought: This is worth a try. So I bought it. &lt;/p&gt;&lt;p&gt;It arrived, and from the moment I opened it, I knew it. &lt;/p&gt;&lt;p&gt;It was the wrong book. &lt;/p&gt;



  

  



  
    
      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                    content-fit
                  "&gt;
                  
                  &lt;img alt="IMG_0749.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558240739-REDNOHTAUCBK61WDDZSN/IMG_0749.jpg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                    content-fit
                  "&gt;
                  
                  &lt;img alt="IMG_0750.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558294592-B41MY4B4U0HZ0EF3U197/IMG_0750.jpg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      
    
  

  










&lt;p&gt;Seems like a neat book, and the experiments could be fun with my kids when they’re a bit older, but this wasn’t the right thing. &lt;/p&gt;&lt;p&gt;So another day, I found myself wandering around AbeBooks, when I realized they had an &lt;a href="https://www.abebooks.com/servlet/SearchEntry" tabindex="0"&gt;Advanced Search&lt;/a&gt; where you could search by year, by publisher, etc. I started working it out: &lt;/p&gt;&lt;ul&gt;
&lt;li&gt;Published between 1985-1999&lt;/li&gt;
&lt;li&gt;Keyword: Science&lt;/li&gt;
&lt;li&gt;Publisher: Reader’s Digest&lt;/li&gt;
&lt;/ul&gt;&lt;p&gt;Wait. That... looks familiar. &lt;/p&gt;&lt;p&gt;Turns out. I have it in my house now. My formative artifact: &lt;/p&gt;











































  

    
  
    

      

      
        &lt;figure class="
              sqs-block-image-figure
              intrinsic
            "&gt;
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                &lt;img alt="" height="2829" src="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/52315c74-73e5-4359-a5ca-fd0f4d932e17/IMG_0751.jpg?format=1000w" width="2193" /&gt;

            
          
        
          
        

        
      
        &lt;/figure&gt;
      

    
  


  


&lt;p&gt;Some of these pages, some of these illustrations are baked into my head deep. &lt;/p&gt;&lt;p&gt;Some of these stories are still how I conceive of things, having never really had to update my knowledge in my head (for example, the story of the coelacanth). &lt;/p&gt;



  

  



  
    
      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                      js-gallery-lightbox-opener
                    
                    content-fill
                  " href="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558379943-DSOZBXE7JD0Q1EHLSN51/IMG_0752.jpeg"&gt;
                  
                    &lt;span class="v6-visually-hidden"&gt;View fullsize&lt;/span&gt;
                  
                  &lt;img alt="IMG_0752.jpeg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558379943-DSOZBXE7JD0Q1EHLSN51/IMG_0752.jpeg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                      js-gallery-lightbox-opener
                    
                    content-fill
                  " href="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558381383-FUC2XG9ZHGR2SNVYHLRL/IMG_0753.jpg"&gt;
                  
                    &lt;span class="v6-visually-hidden"&gt;View fullsize&lt;/span&gt;
                  
                  &lt;img alt="IMG_0753.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558381383-FUC2XG9ZHGR2SNVYHLRL/IMG_0753.jpg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                      js-gallery-lightbox-opener
                    
                    content-fill
                  " href="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558388900-I4VAZTBKCJRHXDP11PZE/IMG_0755.jpeg"&gt;
                  
                    &lt;span class="v6-visually-hidden"&gt;View fullsize&lt;/span&gt;
                  
                  &lt;img alt="IMG_0755.jpeg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558388900-I4VAZTBKCJRHXDP11PZE/IMG_0755.jpeg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                      js-gallery-lightbox-opener
                    
                    content-fill
                  " href="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558392898-L2A1J7530NA2ZR4JGINO/IMG_0756.jpg"&gt;
                  
                    &lt;span class="v6-visually-hidden"&gt;View fullsize&lt;/span&gt;
                  
                  &lt;img alt="IMG_0756.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558392898-L2A1J7530NA2ZR4JGINO/IMG_0756.jpg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                      js-gallery-lightbox-opener
                    
                    content-fill
                  " href="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558400231-T0MUPLRBM0L1C0H1ZV8M/IMG_0757.jpeg"&gt;
                  
                    &lt;span class="v6-visually-hidden"&gt;View fullsize&lt;/span&gt;
                  
                  &lt;img alt="IMG_0757.jpeg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558400231-T0MUPLRBM0L1C0H1ZV8M/IMG_0757.jpeg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                      js-gallery-lightbox-opener
                    
                    content-fill
                  " href="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558401746-998VZVJM5JSGARBF3P21/IMG_0758.jpg"&gt;
                  
                    &lt;span class="v6-visually-hidden"&gt;View fullsize&lt;/span&gt;
                  
                  &lt;img alt="IMG_0758.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/52c2f0cde4b0537e2cba526e/1731558401746-998VZVJM5JSGARBF3P21/IMG_0758.jpg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      
    
  

  













  &lt;p class=""&gt;I have a very strong feeling this book will not leave nearly the impression on my children that it left on me. Yet, I find myself overjoyed at having found it, this little formative artifact. &lt;/p&gt;&lt;p class=""&gt;Dear Reader: I hope you too have yourself a formative artifact. I hope you can find it, and,  maybe yours can be passed on to your own children. A gift they are free to decline, but hey — perhaps they’ll take it. &lt;/p&gt;</description><author>Matthew Gaudet</author><pubDate>Thu, 14 Nov 2024 06:29:15 GMT</pubDate><guid isPermaLink="true">https://www.mgaudet.ca/blog/2024/11/13/a-formative-artifact</guid></item><item><title>Interactive Python Exercises and Quiz</title><link>https://learnbyexample.github.io/interactive-python-exercises/</link><description>&lt;p&gt;Having an interactive program that automatically loads questions and checks the solution is wonderful to have while learning a topic. This &lt;a href="https://github.com/learnbyexample/TUI-apps/tree/main/PythonExercises"&gt;TUI app&lt;/a&gt; has beginner to intermediate level exercises and multiple-choice questions for Python learners.&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for Python exercises" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PythonExercises/python_exercises.png" /&gt;&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="installation"&gt;Installation&lt;a class="zola-anchor" href="#installation"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This app is available on PyPI as &lt;a href="https://pypi.org/project/pythonexercises/"&gt;pythonexercises&lt;/a&gt;. Example installation instructions are shown below, adjust them based on your preferences and OS.&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# virtual environment
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; python3&lt;/span&gt;&lt;span style="color: #5597d6;"&gt; -m&lt;/span&gt;&lt;span&gt; venv textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; cd textual_apps
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; source bin/activate
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pip install pythonexercises
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #7f8989;"&gt;# launch the app
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pythonexercises
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; If you are on Windows, using the &lt;a href="https://en.wikipedia.org/wiki/Windows_Terminal"&gt;Windows Terminal&lt;/a&gt; is recommended. See &lt;a href="https://github.com/learnbyexample/TUI-apps/issues/3#issuecomment-1481488042"&gt;this issue&lt;/a&gt; for Virtual Environment commands and other details.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;To run the app without having to enter the virtual environment again, add this alias to &lt;code&gt;.bashrc&lt;/code&gt; (or equivalent):&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #7f8989;"&gt;# you'll have to change the path
&lt;/span&gt;&lt;span style="color: #b39f04;"&gt;alias &lt;/span&gt;&lt;span style="color: #c23f31;"&gt;pythonexercises&lt;/span&gt;&lt;span style="color: #72ab00;"&gt;=&lt;/span&gt;&lt;span style="color: #d07711;"&gt;'/path/to/textual_apps/bin/pythonexercises'
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As an alternative to manually managing such virtual environments, you can use &lt;a href="https://github.com/pypa/pipx"&gt;https://github.com/pypa/pipx&lt;/a&gt; instead:&lt;/p&gt;
&lt;pre class="language-bash " style="background-color: #f5f5f5; color: #1f1f1f;"&gt;&lt;code class="language-bash"&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pipx install pythonexercises
&lt;/span&gt;&lt;span style="color: #5597d6;"&gt;$&lt;/span&gt;&lt;span&gt; pythonexercises
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As yet another alternative, you can install &lt;code&gt;textual&lt;/code&gt; (see &lt;a href="https://textual.textualize.io/getting_started/"&gt;Textual documentation&lt;/a&gt; for more details), clone this repository and run the &lt;code&gt;python_exercises.py&lt;/code&gt; file. You'll need to install &lt;code&gt;textual[syntax]&lt;/code&gt; to enable syntax highlighting (see &lt;a href="https://textual.textualize.io/widgets/text_area/#syntax-highlighting-dependencies"&gt;documentation&lt;/a&gt; for more details).&lt;/p&gt;
&lt;p&gt;Adjust the terminal dimensions for the widgets to appear properly, for example 84x25 (characters x lines). Here's another screenshot:&lt;/p&gt;
&lt;p align="center"&gt;&lt;img alt="Sample screenshot for Python quiz" src="https://raw.githubusercontent.com/learnbyexample/TUI-apps/main/PythonExercises/python_quiz.png" /&gt;&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="guide"&gt;Guide&lt;a class="zola-anchor" href="#guide"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;See &lt;a href="https://github.com/learnbyexample/TUI-apps/blob/main/PythonExercises/app_guide.md"&gt;app_guide.md&lt;/a&gt; for instructions.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="ebook"&gt;Ebook&lt;a class="zola-anchor" href="#ebook"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The exercise and quiz questions in this app have been adapted from my &lt;a href="https://github.com/learnbyexample/100_page_python_intro"&gt;100 Page Python Intro&lt;/a&gt; ebook.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback"&gt;Feedback&lt;a class="zola-anchor" href="#feedback"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I'd highly appreciate your feedback. Please file &lt;a href="https://github.com/learnbyexample/TUI-apps/issues"&gt;an issue&lt;/a&gt; if there are bugs, crashes, etc.&lt;/p&gt;
&lt;p&gt;Hope you find this TUI app useful. Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Thu, 14 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/interactive-python-exercises/</guid></item><item><title>Installing a Fedora Linux ARM VM from a Raw Image</title><link>https://jasoneckert.github.io/myblog/linux-arm-raw-image/</link><description>&lt;p&gt;Many professionals who work in Information Technology (IT) or software development use an ARM-based PC, such as an Apple Silicon Mac, Snapdragon Elite X Windows Copilot+ PC, or an ARM Linux workstation such as the System76 Thelio Astra. Similarly, many students learning IT or software development may also use an ARM-based PC. For all of these people, it&amp;rsquo;s commonplace to deploy Linux virtual machines (VMs) on a PC using hypervisor software to test cloud apps, host server software, or run specialized tools.&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Thu, 14 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/linux-arm-raw-image/</guid></item><item><title>Maneten - en novell i tre meningar</title><link>https://liza.io/maneten-en-novell-i-tre-meningar/</link><description>&lt;p&gt;Jag har lärt mig svenska mer intensivt på sistone, och nu tänker jag att tiden har kommit för mig att skriva mer på språket. Jag bestämde mig för att skriva en kopia av min allra första novell, som jag skrev på ryska när jag var liten i Ukraina:&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Wed, 13 Nov 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/maneten-en-novell-i-tre-meningar/</guid></item><item><title>Life can be a beach</title><link>https://www.aswathkrishnan.com/2024/11/life-can-be-beach.html</link><description>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/a/AVvXsEiyLJU-bcy1nbDAgGeef3v5NNvUdWb-dt8FZ-v4zNTnc4wZFw4rrW-tto4DqouwB2qZavwh_QsB-CwGhlSFjWC7ekcMeZI6JnlOp1l9YC9sB1dt343KwMsHhHsaNgviEITOu5QOvyRkJP6WCIhQbTAK2JnO7_5dB2Bub4Zhp73wpKx0_EDapGm_CxjBjJWg" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img alt="" height="360" src="https://blogger.googleusercontent.com/img/a/AVvXsEiyLJU-bcy1nbDAgGeef3v5NNvUdWb-dt8FZ-v4zNTnc4wZFw4rrW-tto4DqouwB2qZavwh_QsB-CwGhlSFjWC7ekcMeZI6JnlOp1l9YC9sB1dt343KwMsHhHsaNgviEITOu5QOvyRkJP6WCIhQbTAK2JnO7_5dB2Bub4Zhp73wpKx0_EDapGm_CxjBjJWg=w270-h360" width="270" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;You won’t find many miserable people lounging on a Hawaiian beach. There’s something transformative about the meeting of sea and shore that seems to wash away the burdens of the world. Even toddlers and their usually-under-duress parents are in good spirits. So, I’m only half-joking when I say the beach life might be a good vision for a happier humanity; much better than flying cars, immortality, or space conquests.&amp;nbsp;&lt;/p&gt;&lt;p&gt;Think about it: at the beach, everything’s just... right. You’re not caught up in a to-do list. No meetings, deadlines, or worries. The sun kisses your skin just enough to make you feel alive. The water? Hypnotic, refreshing, and fun. You nap, you read, you write, you frolic, and you just be.&lt;/p&gt;&lt;p&gt;The beach life, both literally and as a metaphor, feels like a piece of paradise. Imagine if we could turn this once-a-year indulgence into a way of being. To those of you who instantly feel the practicality alarms going off, thinking, &lt;em&gt;"But we can't live on vacation!”&lt;/em&gt;—you need some Aloha, my friends. Beach life isn't about perpetually lounging in hammocks, sipping piña coladas; it's about flowing through life with a sense of ease, bliss, freedom, and fulfillment.&lt;/p&gt;&lt;p&gt;And sure, I’m not ignoring the obvious. The only reason I get to kick back on a beach is because someone’s working hard nearby—keeping up the hotel, cooking my meals, maintaining the healthcare systems, all of that. I pay for this with my own work, which is similarly valuable. The challenge isn’t to ignore labor, but to reimagine it: how do we get the essentials done faster, easier, and with less stress so we can beach more? How do we redesign our days so that there’s more room for joy and fewer pointless distractions—like doom-scrolling or chasing hollow status symbols? How do we make space for the good stuff that actually nourishes us?&lt;/p&gt;&lt;p&gt;Beach life is also as much in the mind as it is in what we do and where we are (and they are all inextricably linked). Lucidity, equanimity, optimism, and cheerfulness can color your regular day with the hues of ocean and sand.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;If the beach life sounds appealing, it needn't be a distant dream. Most of us live so mindlessly, so sub-optimally, that repeating this simple exercise can unlock&amp;nbsp;&lt;em&gt;hours&lt;/em&gt;&amp;nbsp;each day within just a few weeks: analyze your day, cut out the junk, automate or streamline what you can (sprinkle some&amp;nbsp;&lt;a href="https://www.aswathkrishnan.com/2023/03/utopai.html" target="_blank"&gt;utopAI&lt;/a&gt; perhaps), and focus on what truly matters. Also&amp;nbsp;&lt;a href="https://www.aswathkrishnan.com/2022/02/basic-shit.html" target="_blank"&gt;do&lt;/a&gt;, say, and&amp;nbsp;&lt;a href="https://www.aswathkrishnan.com/2023/05/how-to-use-prompt-engineering-to-rewire.html"&gt;think&lt;/a&gt;&amp;nbsp;things that put you in a good mindset. Imagine trading hours of mindless chores or stress for moments that actually add value to your life.&amp;nbsp;&lt;/p&gt;&lt;p&gt;I'm naively hopeful that, with a little intention, beach bliss might be well within our reach.&lt;/p&gt;</description><author>Aswath Krishnan</author><pubDate>Wed, 13 Nov 2024 20:04:33 GMT</pubDate><guid isPermaLink="true">https://www.aswathkrishnan.com/2024/11/life-can-be-beach.html</guid></item><item><title>Rethinking selling</title><link>https://ilearnt.com/blog/rethinkingselling/</link><description>&lt;p&gt;Making a sale of a Software as a Service (SaaS) product to a large enterprise customer can be slow and difficult. There are a lot of potential roadblocks that need to be overcome.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Wed, 13 Nov 2024 19:25:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/rethinkingselling/</guid></item><item><title>Quick Tip: How to Fix Stale file handle &amp;amp; RPC: Program not registered between A FreeBSD 14 Server and Linux Client</title><link>https://blog.maxg.io/how-to-fix-stale-random/</link><description>&lt;p&gt;Sometimes, for reasons still a mystery to me, I get an &amp;quot;Stale file handle&amp;quot; error when trying connect to my FreeBSD 14 fileserver&amp;apos;s NFS server. Despite much internet sleuthing &amp;amp; LLM queries, I was not able to solve this until literally just now:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-bash"&gt;service mountd stop&lt;/code&gt;&lt;/pre&gt;</description><author>9μm Pixels</author><pubDate>Wed, 13 Nov 2024 09:44:46 GMT</pubDate><guid isPermaLink="true">https://blog.maxg.io/how-to-fix-stale-random/</guid></item><item><title>chatgpt-shell splits up</title><link>https://xenodium.com/chatgpt-shell-repo-splits-up</link><description>&lt;p&gt;The &lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt; package started as an experiment &lt;a href="https://lmno.lol/alvaro/a-chatgpt-emacs-shell"&gt;glueing the ChatGPT API to an Emacs comint buffer&lt;/a&gt;. Over time, it grew into several packages within the same repository: shell-maker, ob-chatgpt-shell, dall-e-shell, ob-dall-e-shell, and of course chatgpt-shell itself.&lt;/p&gt;
&lt;p&gt;I'm splitting the repository as a first step in reworking &lt;code&gt;chatgpt-shell&lt;/code&gt; to enable multi-model support (i.e. Gemini, Claude, and others), a popular feature request.&lt;/p&gt;
&lt;h2&gt;Want multi-model support?&lt;/h2&gt;
&lt;p&gt;Go 👍 the &lt;a href="https://github.com/xenodium/chatgpt-shell/issues/244"&gt;feature request&lt;/a&gt; and ✨&lt;a href="https://github.com/sponsors/xenodium"&gt;sponsor&lt;/a&gt;✨ the work.&lt;/p&gt;
&lt;p&gt;If keen on having a multi-modal &lt;code&gt;chatgpt-shell&lt;/code&gt; at your fingertips, please consider &lt;a href="https://github.com/sponsors/xenodium"&gt;sponsoring&lt;/a&gt; to make the project sustainable. Improvements like this, integrations, and keeping up with the AI space takes quite a bit of work and effort.&lt;/p&gt;
&lt;h2&gt;New package repositories&lt;/h2&gt;
&lt;h3&gt;chatgpt-shell&lt;/h3&gt;
&lt;p&gt;No repo location changes. Remains at &lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;https://github.com/xenodium/chatgpt-shell&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chatgpt-shell&lt;/code&gt; carries the ChatGPT shell itself, but also convenience integrations.&lt;/p&gt;
&lt;p&gt;My hope is to make this a multi-model package.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/chatgpt-shell-repo-splits-up/swiftui.webp" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/chatgpt-shell-repo-splits-up/japanese.webp" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/chatgpt-shell-repo-splits-up/fix.webp" /&gt;&lt;/p&gt;
&lt;h3&gt;ob-chatgpt-shell&lt;/h3&gt;
&lt;p&gt;Moves to &lt;a href="https://github.com/xenodium/ob-chatgpts-shell"&gt;https://github.com/xenodium/ob-chatgpts-shell&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;An extension of &lt;code&gt;chatgpt-shell&lt;/code&gt; to execute org babel blocks as ChatGPT prompts.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/chatgpt-shell-repo-splits-up/ob-chatgpt-shell.png" /&gt;&lt;/p&gt;
&lt;h3&gt;dall-e-shell&lt;/h3&gt;
&lt;p&gt;Moves to &lt;a href="https://github.com/xenodium/dall-e-shell"&gt;https://github.com/xenodium/dall-e-shell&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;A dedicated shell for DALL-E image generation.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/chatgpt-shell-repo-splits-up/dall-e-shell.png" /&gt;&lt;/p&gt;
&lt;h3&gt;ob-dall-e-shell&lt;/h3&gt;
&lt;p&gt;Moves to &lt;a href="https://github.com/xenodium/ob-dall-e-shell"&gt;https://github.com/xenodium/ob-dall-e-shell&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;An extension of &lt;code&gt;dall-e-shell&lt;/code&gt; to execute org babel blocks as ChatGPT prompts.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/chatgpt-shell-repo-splits-up/ob-dall-e-shell.png" /&gt;&lt;/p&gt;
&lt;h3&gt;shell-maker&lt;/h3&gt;
&lt;p&gt;Moves to &lt;a href="https://github.com/xenodium/shell-maker"&gt;https://github.com/xenodium/shell-maker&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;shell-maker&lt;/code&gt; a convenience wrapper around &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Shell-Prompts.html"&gt;comint mode&lt;/a&gt; to build shells. Both &lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt; and &lt;a href="https://github.com/xenodium/dall-e-shell"&gt;dall-e-shell&lt;/a&gt; are built on top of shell-maker.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/chatgpt-shell-repo-splits-up/sofia.gif" /&gt;&lt;/p&gt;
&lt;h2&gt;Enjoying this content? Using one of my Emacs packages?&lt;/h2&gt;
&lt;p&gt;Help make the work sustainable. Consider &lt;a href="https://github.com/sponsors/xenodium"&gt;sponsoring&lt;/a&gt;. I'm also building &lt;a href="https://lmno.lol/"&gt;lmno.lol&lt;/a&gt;. A platform to &lt;a href="https://indieweb.social/@xenodium/112265481282475542"&gt;drag and drop&lt;/a&gt; your blog to the web.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Wed, 13 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/chatgpt-shell-repo-splits-up</guid></item><item><title>Healthy Things Grow</title><link>https://www.danstroot.com/posts/2024-09-27-healthy-things-grow</link><description>&lt;img alt="post image" src="https://danstroot.imgix.net/assets/blog/img/forest.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;Gardeners don't 'create' or 'produce' tomatoes. They can only create an environment where tomato plants will thrive. The point is if the environment is healthy, growth happens. There are a lot of factors to consider - the quality of the soil, the weather, pests... the list goes on. Similarly, how do we create healthy growth in our organizations?&lt;br /&gt;&lt;br /&gt;This post &lt;a href="https://www.danstroot.com/posts/2024-09-27-healthy-things-grow"&gt;Healthy Things Grow&lt;/a&gt; first appeared on &lt;a href="https://www.danstroot.com"&gt;Dan Stroot's Blog&lt;/a&gt;</description><author>Dan Stroot</author><pubDate>Wed, 13 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danstroot.com/posts/2024-09-27-healthy-things-grow</guid></item><item><title>The Pirate Bay på SVT</title><link>https://liza.io/the-pirate-bay-p%C3%A5-svt/</link><description>&lt;p&gt;Jag har börjat titta på &lt;a href="https://www.svtplay.se/the-pirate-bay"&gt;The Pirate Bay på SVT Play&lt;/a&gt; den här veckan, och den är asbra! Serien handlar om hur, vem, och när The Pirate Bay var utvecklades i Sverige. Den har en riktig &amp;ldquo;Hackers&amp;rdquo; och &amp;ldquo;Halt and Catch Fire&amp;rdquo; vibe. Desvärre finns det bara två avsnitt förtfarande, och jag slukade båda i ett nafs.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Tue, 12 Nov 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/the-pirate-bay-p%C3%A5-svt/</guid></item><item><title>Are reviews broken?</title><link>https://ilearnt.com/blog/reviewstars/</link><description>&lt;p&gt;A mobile app developer was frustrated recently by receiving a one star review for their application.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Tue, 12 Nov 2024 22:19:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/reviewstars/</guid></item><item><title>Waters of the Pacific</title><link>https://urda.com/blog/2024/11/12/pacifica</link><description>I bathed in the waters of the Pacific and I was born again. I was molded and perfected by the startup battlegrounds of San Francisco, my character and ability were put to the test at Amazon, and now I am building the road to space for future generations using everything I have learned so far. This is my engineering story.</description><author>Writings of Urda</author><pubDate>Tue, 12 Nov 2024 18:00:00 GMT</pubDate><guid isPermaLink="true">https://urda.com/blog/2024/11/12/pacifica</guid></item><item><title>When generating apps the spec is important</title><link>https://paul.kinlan.me/the-spec-is-important/</link><description>Generating web apps with AI agents like Replit is incredibly powerful, enabling rapid prototyping and deployment.  My experience building tldr.express, a personalized RSS feed summarizer, highlighted the importance of a detailed specification. While initial prompts yielded impressive results, I iteratively refined the app through configuration and additional prompts to address issues like email integration, AI model selection, output formatting, spam prevention, and bot mitigation.  This iterative process reinforced that while AI agents excel at rapid generation, a well-defined specification upfront is crucial for a successful outcome.</description><author>Modern Web Development with Chrome</author><pubDate>Tue, 12 Nov 2024 12:42:00 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/the-spec-is-important/</guid></item><item><title>A little bit of delusion</title><link>https://martinrue.com/a-little-bit-of-delusion</link><description>94% of people believe they're above average, but a little bit of delusion is a good thing.</description><author>Martin Rue</author><pubDate>Tue, 12 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/a-little-bit-of-delusion</guid></item><item><title>CSS fix to prevent orphan icons dropping to a new line</title><link>https://muffinman.io/blog/css-fix-to-prevent-orphan-icons/</link><description>&lt;article class="article"&gt;
&lt;p&gt;When an HTML element becomes too narrow, its content starts to wrap into multiple lines. This is intended behavior and works well in many cases. However, for short text, it doesn&amp;#x27;t look great when the last word or icon drops to the next line, becoming an &lt;em&gt;orphan&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;For example, you might see something like this:&lt;/p&gt;
&lt;div class="demo-text demo-text-example"&gt;Click here for more info&lt;br /&gt;&lt;svg class="orphan-icon" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"&gt;&lt;path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"&gt;&lt;/path&gt; &lt;path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/div&gt;
&lt;p&gt;It doesn&amp;#x27;t look great when the icon is left alone on its own row. Luckily, there&amp;#x27;s a simple CSS solution.&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution &lt;a class="anchor-link" href="#solution"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Only thing we need to add is a single line of CSS:&lt;/p&gt;
&lt;pre class="language-css"&gt;&lt;code class="language-css code-highlight"&gt;&lt;span class="code-line"&gt;&lt;span class="token property"&gt;text-wrap&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; balance&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This property tells the browser to wrap the text in a way that best balances the number of characters on each line, enhancing layout quality and legibility. The browser will calculate this for us, preventing orphaned icons or words.&lt;/p&gt;
&lt;h3 id="text-wrap-pretty"&gt;text-wrap: pretty &lt;a class="anchor-link" href="#text-wrap-pretty"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There is also the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/CSS/text-wrap#pretty"&gt;pretty&lt;/a&gt; value for text-wrap, which is even better. It works in a similar way, but the browser will use a slower algorithm that prioritizes better layout over speed.&lt;/p&gt;
&lt;p&gt;Unfortunately, at the time of writing, &lt;code&gt;pretty&lt;/code&gt; is not supported in Firefox nor Safari. However, you can define both values, and browsers that don&amp;#x27;t support &lt;code&gt;pretty&lt;/code&gt; will fall back to &lt;code&gt;balance&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="language-css"&gt;&lt;code class="language-css code-highlight"&gt;&lt;span class="code-line"&gt;&lt;span class="token property"&gt;text-wrap&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; balance&lt;span class="token punctuation"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="code-line"&gt;&lt;span class="token property"&gt;text-wrap&lt;/span&gt;&lt;span class="token punctuation"&gt;:&lt;/span&gt; pretty&lt;span class="token punctuation"&gt;;&lt;/span&gt; &lt;span class="token comment"&gt;/* This will be ignored by browsers that don&amp;#x27;t support it. */&lt;/span&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="demo"&gt;Demo &lt;a class="anchor-link" href="#demo"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Here&amp;#x27;s a simple demo. Use the handle on the right to resize the element and see how text breaks with and without the &lt;code&gt;balance&lt;/code&gt; property applied.&lt;/p&gt;
&lt;div class="demo"&gt;&lt;label&gt;&lt;input checked="" name="wrap" type="radio" value="wrap" /&gt;&lt;span&gt;Default&lt;/span&gt;&lt;/label&gt;&lt;label&gt;&lt;input name="wrap" type="radio" value="balance" /&gt;&lt;code&gt;text-wrap: &lt;span&gt;balance&lt;/span&gt;&lt;/code&gt;&lt;/label&gt;&lt;label class="pretty-label"&gt;&lt;input name="wrap" type="radio" value="pretty" /&gt;&lt;code&gt;text-wrap: &lt;span&gt;pretty&lt;/span&gt;&lt;/code&gt;&lt;/label&gt;&lt;div class="pretty-notice notice"&gt;Unfortunately your browser doesn&amp;#x27;t support &lt;code&gt;text-wrap: pretty&lt;/code&gt;.&lt;/div&gt;&lt;div class="demo-resize"&gt;&lt;div class="demo-resize-handle" title="Drag to resize"&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div class="demo-text"&gt;Click here for more info &lt;svg class="orphan-icon" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"&gt;&lt;path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"&gt;&lt;/path&gt; &lt;path d="m8.93 6.588-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533zM9 4.5a1 1 0 1 1-2 0 1 1 0 0 1 2 0"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/div&gt;&lt;div class="demo-text"&gt;A somewhat longer text that will break into multiple lines &lt;svg class="orphan-icon" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"&gt;&lt;path d="M1 8a.5.5 0 0 1 .5-.5h11.793l-3.147-3.146a.5.5 0 0 1 .708-.708l4 4a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708L13.293 8.5H1.5A.5.5 0 0 1 1 8" fill-rule="evenodd"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;/article&gt;</description><author>Muffin Man</author><pubDate>Tue, 12 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://muffinman.io/blog/css-fix-to-prevent-orphan-icons/</guid></item><item><title>2024-11-12</title><link>https://ho.dges.online/pictures/2024-11-12/</link><description/><author>ho.dges.online</author><pubDate>Tue, 12 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-11-12/</guid></item><item><title>Flouting the Internet Protocols with Tunnels</title><link>https://www.macchaffee.com/blog/2024/tunneling/</link><description>&lt;p&gt;Recently at work I've learned about &lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/"&gt;Cloudflare Tunnels&lt;/a&gt;, which has increased my interest in tunneling technologies in general.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Tunneling_protocol"&gt;Tunneling&lt;/a&gt; is a generic networking term, but in the web-dominated world it usually refers to software that you can run on any computer with outbound network access to serve a website. The tunneling software has a another component running in the cloud that configures DNS, terminates TLS, and handles the networking magic to send the packets to your computer.&lt;/p&gt;
&lt;p&gt;My main interest is using tunneling for serving websites on my home server. While I'm fortunate to have a fairly static IP address, I don't want to risk having to update my DNS records after a power outage, and I don't like having to port-forward. I say "sites" plural, but I really just have one site, which is an instance of the open-source recipe site &lt;a href="https://mealie.io/"&gt;Mealie&lt;/a&gt; at &lt;code&gt;recipes.macchaffee.com&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;I feel like by using tunnels, the authors of various internet protocols are rolling in their graves (wait, are they mostly still alive?), but hey I've got websites that need to be deployed and my ISP won't help me do it.&lt;/p&gt;
&lt;h2 id="choosing-the-right-tunnel"&gt;Choosing the right tunnel&lt;/h2&gt;
&lt;p&gt;After skimming through the &lt;a href="https://github.com/anderspitman/awesome-tunneling"&gt;awesome-tunneling&lt;/a&gt; list, I struggle to find a better solution than Cloudflare Tunnels, much to my chagrin as someone who fears &lt;a href="https://www.macchaffee.com/blog/2024/ddos-attacks/"&gt;the centralization of the Internet&lt;/a&gt;. My ideal features include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Designed for production use, not just development.&lt;/li&gt;
&lt;li&gt;Good protocol choice for the tunnel itself, such as WireGuard or HTTP/3.&lt;/li&gt;
&lt;li&gt;Multi-regional, including integration with a CDN for caching (why not make the most of the extra network hop?).&lt;/li&gt;
&lt;li&gt;Written in a performant, memory-safe language.&lt;/li&gt;
&lt;li&gt;Extra server-side features like metrics, authentication, and everything else you'd expect from a modern load balancer.&lt;/li&gt;
&lt;li&gt;Open-source.&lt;/li&gt;
&lt;li&gt;Not a Cloudflare product.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;So to any older CDN providers looking to capture a piece of the modern stack, there's your next idea!&lt;/p&gt;
&lt;p&gt;With Cloudflare Tunnels out of the picture, I ended up compromising on #2, #3, and #5 on my wishlist and settled on &lt;a href="https://pico.sh/tuns"&gt;tuns&lt;/a&gt;. They also offer &lt;a href="https://pico.sh/pgs"&gt;static site hosting&lt;/a&gt; with features I wanted, so I took the bundle deal and got both.&lt;/p&gt;
&lt;p&gt;The client-side part of tuns is just &lt;code&gt;ssh -R&lt;/code&gt;, which makes it very easy to get started. There's also &lt;a href="https://github.com/picosh/tunmgr"&gt;tunmgr&lt;/a&gt; which is less than 1k lines of Go code, mainly just wrapping the &lt;code&gt;ssh -R&lt;/code&gt; equivalent from Golang's standard library, plus some optional Docker integration.&lt;/p&gt;
&lt;p&gt;With my choice of tunneling software made, I moved on to the fun part: deploying tuns to serve traffic to my recipe site hosted on my home server.&lt;/p&gt;
&lt;h2 id="deploying"&gt;Deploying&lt;/h2&gt;
&lt;p&gt;First, a short summary of my homelab setup: It's an Intel NUC minicomputer running &lt;a href="https://nixos.org/"&gt;NixOS&lt;/a&gt; which is running &lt;a href="https://docs.k3s.io/"&gt;k3s&lt;/a&gt; (a minimal Kubernetes distribution). Everything's deployed from a git repo using &lt;a href="https://fluxcd.io/"&gt;FluxCD&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So since tunmgr doesn't provide a Kubernetes installation mechanism, I made it myself (&lt;a href="https://github.com/picosh/tunmgr/pull/2"&gt;and contributed it&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;The steps remaining were:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Generate a new SSH key.&lt;/li&gt;
&lt;li&gt;Add it to my Pico account.&lt;/li&gt;
&lt;li&gt;Also add it as a &lt;a href="https://www.macchaffee.com/blog/2022/k8s-secrets/"&gt;Kubernetes Secret&lt;/a&gt; (encrypted in git with &lt;a href="https://github.com/getsops/sops"&gt;SOPS&lt;/a&gt;) to my cluster.&lt;/li&gt;
&lt;li&gt;Deploy tunmgr (a single Deployment, with locked-down permissions and even an egress NetworkPolicy to ensure it only talks to the &lt;code&gt;tuns.sh&lt;/code&gt; server).&lt;/li&gt;
&lt;li&gt;Configure tunmgr to direct all traffic for &lt;code&gt;recipes.macchaffee.com&lt;/code&gt; to &lt;a href="https://doc.traefik.io/traefik/"&gt;Traefik&lt;/a&gt; (this is needed to obtain normal load balancer features like metrics and IP allow-listing, which will come into play later).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After configuring my DNS and waiting a short delay for the TLS cert to be provisioned, my recipes site was up and running! No port-forwarding required! Here's what it looks like:&lt;/p&gt;
&lt;figure&gt;
  &lt;img alt="A diagram showing traffic flowing from the tuns server, into a box representing my home server. Inside the home server box, the traffic first hits tunmgr, then traefik, then mealie." src="https://www.macchaffee.com/blog/2024/tuns-diagram.png" /&gt;
  &lt;figcaption&gt;&lt;em&gt;Not pictured: hundreds of lines of YAML&lt;/em&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;But then tragedy struck. As soon as the hostname &lt;code&gt;recipes.macchaffee.com&lt;/code&gt; landed in the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/Security/Certificate_Transparency"&gt;Certificate Transparency&lt;/a&gt; logs, I was reminded that the internet is a hostile place since I immediately received requests from scanners searching for vulnerabilities or sensitive files (a &lt;a href="https://blog.apnic.net/2023/08/30/certifiably-vulnerable-using-certificate-transparency-logs-for-target-reconnaissance/"&gt;common occurance&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;I constantly struggle with the balance between hosting "public" sites that aren't &lt;em&gt;too&lt;/em&gt; public, including this blog. My recipe site is the same. I want to be able to access it from outside my house, and I want my friends and family to be able to access it too without having to set up a VPN client or keep track of a password. But I don't want random scanners to eventually find a vulnerability in Mealie and automatically deploy some malware straight out of a scifi horror movie on my home network.&lt;/p&gt;
&lt;p&gt;So I needed to build the Internet equivalent of a "No tresspassing" sign, which I call &lt;a href="https://github.com/mac-chaffee/ip-pass"&gt;&lt;code&gt;ip-pass&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h1 id="the-creation-of-ip-pass"&gt;The creation of ip-pass&lt;/h1&gt;
&lt;p&gt;My software engineer skills have been collecting dust ever since I moved into DevOps and started "coding" in YAML, but I dusted them off and started up &lt;a href="https://github.com/mac-chaffee/ip-pass"&gt;a new Golang project&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;This was also a good excuse to practice AI-assisted coding, since I've read a lot more Golang than I've written as a result of spending years debugging issues in various &lt;a href="https://www.cncf.io/"&gt;CNCF&lt;/a&gt; projects. I just used &lt;a href="https://claude.ai/"&gt;Claude&lt;/a&gt; for some things instead of scrolling through GitHub to find an example to copy/paste. The only interesting thing that happened is Claude generated a test case with the IP address &lt;code&gt;203.0.113.333&lt;/code&gt; which is invalid. Thankfully the test failed and caught it, because I didn't notice. I think this is a lesson that "just have a human check the AI's output" will never work because AI easily generates more output than humans have attention. You need strict automated checks (like a compiler) for this kind of thing.&lt;/p&gt;
&lt;p&gt;The project is one giant &lt;code&gt;main.go&lt;/code&gt; file which starts an HTTP server which reads the user's IP address and uses the Kubernetes &lt;code&gt;client-go&lt;/code&gt; to add the IP to an allow list. Since I'm using Traefik, ip-pass updates a &lt;a href="https://doc.traefik.io/traefik/middlewares/http/ipallowlist/"&gt;Traefik Middleware&lt;/a&gt; that contains the allow-list.&lt;/p&gt;
&lt;p&gt;There's a web interface too, which I've deployed to &lt;code&gt;access.macchaffee.com&lt;/code&gt; (also via &lt;code&gt;tuns&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;When you go to &lt;code&gt;access.macchaffee.com&lt;/code&gt;, you click a big green "Gain Access" button which allow-lists your IP address and redirects you to &lt;code&gt;recipes.macchaffee.com&lt;/code&gt;. Since the recipes site blocks all IPs not on the allow-list, bots are thwarted, but anyone with a brain and a browser can still get in with minimal hassle.&lt;/p&gt;
&lt;p&gt;Obviously this has no &lt;em&gt;real&lt;/em&gt; security benefits because it's just security by obscurity. It's like changing your default SSH port to something other than port 22. But I think it hits a sweet spot on the &lt;a href="https://blog.c3l-security.com/2019/06/balancing-functionality-usability-and.html"&gt;security, usability, functionality triangle&lt;/a&gt;.&lt;/p&gt;</description><author>Mac's Tech Blog</author><pubDate>Tue, 12 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.macchaffee.com/blog/2024/tunneling/</guid></item><item><title>Getting comfortable with psql</title><link>/2024/11/11/Getting-comfortable-with-psql/</link><description>&lt;p&gt;psql is a CLI editor that ships with Postgres. It&amp;rsquo;s incredibly powerful for working with Postgres, and doesn&amp;rsquo;t take too much of a learning curve to start to get comfortable so you can really feel like an expert working with your database.&lt;/p&gt;
&lt;p&gt;Just a rundown of a few things to get you started:&lt;/p&gt;
&lt;p&gt;Once connected in psql you can get an idea of all utility commands available with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sql"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;\&lt;/span&gt;&lt;span style="color: #f92672;"&gt;?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A handy thing I use all the time is &lt;code&gt;\d&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;\d&lt;/code&gt; will describe the tables within database. You can also add a table/index/etc. onto it to describe that specific table such as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sql"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;\&lt;/span&gt;d accounts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There are a number of options you can set in your psqlrc (config) file to customize your CLI experience. But you can also toggle those when directly working in psql.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\timing&lt;/code&gt; will give you the time it took to run your query&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\x auto&lt;/code&gt; will autoformat your text output&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\pset pager 0&lt;/code&gt; turns off your pager or 1 to turn it back on&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Oh and for editing a query in your editor of choice. Make sure you set your $EDITOR enviroment variable to the editor of your choice, though the only right choice is vim:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sql"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #960050; background-color: #1e0010;"&gt;\&lt;/span&gt;e
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Just a few things to get you started working with psql.&lt;/p&gt;
&lt;video class="video-shortcode" controls="controls" preload="auto"&gt;
&lt;source alt="Psql basics" src="https://craigkerstiens.b-cdn.net/psql_basics.mp4" type="video/mp4" /&gt;
Your browser does not support video
&lt;/video&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Mon, 11 Nov 2024 17:57:56 GMT</pubDate><guid isPermaLink="true">/2024/11/11/Getting-comfortable-with-psql/</guid></item><item><title>Are AI Assistants Making Us Worse Programmers?</title><link>https://rafaelquintanilha.com/are-ai-assistants-making-us-worse-programmers/</link><description>Perspectives on the rise of AI in programming and its effects on productivity.</description><author>Software Engineering - Rafael Quintanilha</author><pubDate>Mon, 11 Nov 2024 16:33:45 GMT</pubDate><guid isPermaLink="true">https://rafaelquintanilha.com/are-ai-assistants-making-us-worse-programmers/</guid></item><item><title>Reproductive Biology for Politicians and Voters, Part 1</title><link>https://denovo.substack.com/p/reproductive-biology-for-politicians</link><description>Embryos, IVF, and fetal development</description><author>De Novo</author><pubDate>Mon, 11 Nov 2024 14:49:27 GMT</pubDate><guid isPermaLink="true">https://denovo.substack.com/p/reproductive-biology-for-politicians</guid></item><item><title>When is read-only not read-only?</title><link>https://raesene.github.io/blog/2024/11/11/When-Is-Read-Only-Not-Read-Only/</link><description>&lt;p&gt;Bit of a digression from the network series today, to discuss something I just saw in passing which is an interesting example of a possible sharp corner/foot gun in Kubernetes RBAC.&lt;/p&gt;

&lt;p&gt;Generally speaking for REST style APIs &lt;code class="language-plaintext highlighter-rouge"&gt;GET&lt;/code&gt; requests are read-only, so shouldn’t change the state of resources or execute commands. As such you might think that giving a user the following rights in Kubernetes would essentially just be giving them read-only access to pod information in the default namespace.&lt;/p&gt;

&lt;div class="language-yaml highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Role&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pod-reader&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods/log"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods/status"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods/exec"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods/attach"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods/portforward"&lt;/span&gt;
  &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;However due to the details of how Websockets works with Kubernetes, this access &lt;em&gt;can&lt;/em&gt; allow for users to run &lt;code class="language-plaintext highlighter-rouge"&gt;kubectl exec&lt;/code&gt; commands in pods and get command execution rights in that namespace! There’s information on the origins of this in &lt;a href="https://github.com/kubernetes/kubernetes/issues/78741"&gt;this Github issue&lt;/a&gt; but it’s essentially down to how websockets works.&lt;/p&gt;

&lt;p&gt;What’s possibly more interesting is that, while this behaviour has been in place for a while you might not have noticed it, as the default in Kubernetes was to use &lt;a href="https://en.wikipedia.org/wiki/SPDY"&gt;SPDY&lt;/a&gt; for &lt;code class="language-plaintext highlighter-rouge"&gt;exec&lt;/code&gt; commands instead of websockets, until Kubernetes version 1.31. So if a user with &lt;code class="language-plaintext highlighter-rouge"&gt;GET&lt;/code&gt; rights on &lt;code class="language-plaintext highlighter-rouge"&gt;pods/exec&lt;/code&gt; tried to use &lt;code class="language-plaintext highlighter-rouge"&gt;kubectl exec&lt;/code&gt; in 1.29 you’d get an error like this&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;Error from server (Forbidden): pods "test" is forbidden: User "bob" cannot create resource "pods/exec" in API group "" in the namespace "default"
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;but if a user with the exact same rights, tried the same command in Kubernetes 1.31 it works!&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;kubectl --kubeconfig bob.config exec -it test -- /bin/bash
bash-5.1# exit
exit
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s worth noting that, whilst it’s easier to do now, using websockets with these rights has been possible for a long time using tools like &lt;a href="https://github.com/jpts/kubectl-execws"&gt;kubectl-execws&lt;/a&gt; from &lt;a href="https://hachyderm.io/@jpts"&gt;jpts&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;Kubernetes RBAC has some tricky areas where the behaviour you get might not be exactly what you expect, and sometimes as in this case, those unexpected behaviours are not very apparent!&lt;/p&gt;</description><author>Raesene's Ramblings</author><pubDate>Mon, 11 Nov 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">https://raesene.github.io/blog/2024/11/11/When-Is-Read-Only-Not-Read-Only/</guid></item><item><title>In Cold Blood by Truman Capote</title><link>https://apurva-shukla.me/bookshelf/in-cold-blood/</link><description>⭐ ⭐ ⭐ In Cold Blood by Truman Capote was a tale it feels you’d hear from a stranger at a bar, detailing a series of events that seem…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Mon, 11 Nov 2024 13:57:36 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/in-cold-blood/</guid></item><item><title>Rich losers</title><link>https://eftegarie.com/rich-losers/</link><description>There are people who are rich, yet who don&amp;#8217;t earn my respect. Because they&amp;#8217;ve become rich not by serving others or producing products that people want, but by doing things that only benefit themselves. I call them rich losers. They&amp;#8217;re often involved in things like crypto gambling, dropshipping, playing poker or malicious affiliate marketing. If [&amp;#8230;]</description><author>Amin Eftegarie</author><pubDate>Mon, 11 Nov 2024 06:38:39 GMT</pubDate><guid isPermaLink="true">https://eftegarie.com/rich-losers/</guid></item><item><title>Your Wi-Fi might be terrible because of Dynamic Frequency Selection (DFS)</title><link>https://ounapuu.ee/posts/2024/11/11/openwrt-dfs/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/11/11/openwrt-dfs/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;For a few months, I had issues with my Wi-Fi network. The 2.4 GHz network would be fine, but the 5 GHz one would
suddenly stop working and completely disappear from the available Wi-Fi networks. OpenWRT upgrades also didn&amp;rsquo;t improve
the situation. This was very annoying.&lt;/p&gt;
&lt;p&gt;After some discussions with a friend, I learned
about &lt;a href="https://en.wikipedia.org/wiki/Dynamic_frequency_selection"&gt;Dynamic Frequency Selection (DFS).&lt;/a&gt; Apparently some
channels on the 5 GHz Wi-Fi spectrum are also used by weather and military radars, and those take priority. If such
interference is detected, your Wi-Fi access point should switch to a different channel.&lt;/p&gt;
&lt;p&gt;It turns out that some implementations are buggy and mine is one of them.&lt;/p&gt;
&lt;p&gt;One quick but permanent fix is to manually select a Wi-Fi channel to operate in.&lt;/p&gt;
&lt;p&gt;With OpenWRT, you can get a list of all the available Wi-Fi channels using this command:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;iw list | grep dBm
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Example output:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;* 2412 MHz [1] (20.0 dBm)
* 2417 MHz [2] (20.0 dBm)
* 2422 MHz [3] (20.0 dBm)
* 2427 MHz [4] (20.0 dBm)
* 2432 MHz [5] (20.0 dBm)
* 2437 MHz [6] (20.0 dBm)
* 2442 MHz [7] (20.0 dBm)
* 2447 MHz [8] (20.0 dBm)
* 2452 MHz [9] (20.0 dBm)
* 2457 MHz [10] (20.0 dBm)
* 2462 MHz [11] (20.0 dBm)
* 2467 MHz [12] (20.0 dBm)
* 2472 MHz [13] (20.0 dBm)
* 5180 MHz [36] (23.0 dBm)
* 5200 MHz [40] (23.0 dBm)
* 5220 MHz [44] (23.0 dBm)
* 5240 MHz [48] (23.0 dBm)
* 5260 MHz [52] (20.0 dBm) (radar detection)
* 5280 MHz [56] (20.0 dBm) (radar detection)
* 5300 MHz [60] (20.0 dBm) (radar detection)
* 5320 MHz [64] (20.0 dBm) (radar detection)
* 5500 MHz [100] (26.0 dBm) (radar detection)
* 5520 MHz [104] (26.0 dBm) (radar detection)
* 5540 MHz [108] (26.0 dBm) (radar detection)
* 5560 MHz [112] (26.0 dBm) (radar detection)
* 5580 MHz [116] (26.0 dBm) (radar detection)
* 5600 MHz [120] (26.0 dBm) (radar detection)
* 5620 MHz [124] (26.0 dBm) (radar detection)
* 5640 MHz [128] (26.0 dBm) (radar detection)
* 5660 MHz [132] (26.0 dBm) (radar detection)
* 5680 MHz [136] (26.0 dBm) (radar detection)
* 5700 MHz [140] (26.0 dBm) (radar detection)
* 5720 MHz [144] (13.0 dBm) (radar detection)
* 5745 MHz [149] (13.0 dBm)
* 5765 MHz [153] (13.0 dBm)
* 5785 MHz [157] (13.0 dBm)
* 5805 MHz [161] (13.0 dBm)
* 5825 MHz [165] (13.0 dBm)
* 5845 MHz [169] (13.0 dBm)
* 5865 MHz [173] (13.0 dBm)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Notice the ones with &lt;code&gt;(radar detection)&lt;/code&gt; at the end? Those are the potentially problematic channels. We&amp;rsquo;re going to
avoid them from now on by picking a specific channel to use.&lt;/p&gt;
&lt;p&gt;When it comes to the choice of the channels themselves, you&amp;rsquo;ll also have to consider the channel width. If you pick a
channel next to one of the radar detection ones and with a large channel width, you might still run into issues.&lt;/p&gt;
&lt;p&gt;Choosing a specific channel also comes with bandwidth and range trade-offs. If you don&amp;rsquo;t care much for those, go for the
lowest one and with 40 MHz width. Picking the optimal Wi-Fi channel and channel width configuration for your specific
needs is better explained by other resources.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/11/11/openwrt-dfs/media/openwrt-luci.png"&gt;
        &lt;img alt="Choosing a specific channel in OpenWRT using the GUI (LuCI)." height="462" src="https://ounapuu.ee/posts/2024/11/11/openwrt-dfs/media/openwrt-luci_hu_ca84641e8077501a.png" style="width: auto; height: auto; border-radius: 8px;" width="902" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Choosing a specific channel in OpenWRT using the GUI (LuCI).
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;I recommend getting a Wi-Fi spectrum analysis app that shows you the channels that are least populated by neighboring
Wi-Fi access points.&lt;/p&gt;
&lt;p&gt;It is also possible to define the list of channels that the Wi-Fi AP can automatically choose from using the &lt;code&gt;channels&lt;/code&gt;
option for the wireless interface. We can use this setting to avoid the radar detection channels completely. This
setting doesn&amp;rsquo;t seem to be configurable via the graphical interface (LuCI), but you can change it
in &lt;code&gt;/etc/config/wireless&lt;/code&gt; using the command line and &lt;code&gt;vi&lt;/code&gt;, over SSH.&lt;/p&gt;
&lt;p&gt;More information about this option and others can be found
in &lt;a href="https://openwrt.org/docs/guide-user/network/wifi/basic#common_options"&gt;OpenWRT documentation.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Based on our example, a configuration that avoids radar detection frequencies can look something like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;config wifi-device 'radio0'
        option type 'mac80211'
        option path 'pci0000:00/0000:00:00.0'
        option channel 'auto'
        option channels '36 40 44 48 149 153 157 161 165 169 173'
        option band '5g'     
        option htmode 'VHT40'
        option country 'EE'    
        option cell_density '0'
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Using a manually specified channel has resulted in no Wi-Fi related issues for over half a year.&lt;/p&gt;
&lt;p&gt;I consider this a permanent fix.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re using a PC and don&amp;rsquo;t want to mess with Wi-Fi issues ever again, then just run some Ethernet cables. It&amp;rsquo;s worth
it.&lt;/p&gt;</description><author>./techtipsy</author><pubDate>Mon, 11 Nov 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/11/11/openwrt-dfs/</guid></item><item><title>Supporting coworkers, employees, and friends in this time</title><link>https://ntietz.com/blog/supporting-friends-and-coworkers-in-this-time/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;We should &lt;em&gt;always&lt;/em&gt; be supporting each other, but it feels particularly important right now.
An election just finished in the US, which means that half the country lost and has to face the coming changes.
In particular, this is a scary time for many folks who have been targets in the past couple of years, with escalating legislation against access to gender-affirming care.&lt;/p&gt;
&lt;p&gt;So, what do we do concretely right now to help each other?
This has two parts: what folks need, and how best to help meet those needs.
I hope this list helps everyone see that none of us is powerless, and each of us has things we can do, today, to make a difference.
If you're affected by recent events, you can keep this list handy as an answer when someone asks "what can I do to support you?"&lt;/p&gt;
&lt;h1 id="what-folks-might-need-right-now"&gt;What folks might need right now&lt;/h1&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Some slack.&lt;/strong&gt;
When folks are afraid, it's &lt;em&gt;very&lt;/em&gt; hard to sustain excellent work output.
Right now, people might be in need of some slack and latitude in what they're doing.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commitments for access to gender-affirming care.&lt;/strong&gt;
Many states have already passed laws restricting access to gender-affirming care, with Florida being perhaps the worst.
This seems like a likely priority at the federal level, as well.
Most Americans get our insurance through our employers, so it's not something we have a lot of choice over.
We need to know that, even if it's not covered by the insurance company, that our employer will cover this care for us.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Commitments for access to reproductive healthcare.&lt;/strong&gt;
There is a risk of restrictions on access to everything ranging from birth control to vasectomies to abortions.
This will be targeted at a state level and at the federal level.
Just as with gender-affirming care, it's important to know that employers cover this care for us and provide as much assistance as they can.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Remote work.&lt;/strong&gt;
This is important because if folks are living in an area that restricts access to medical care, they may have to relocate.
Among other reasons that it's always a good idea, it's especially important now.
This means &lt;em&gt;remote&lt;/em&gt; work, not just a hybrid model, because it may become unsafe to travel in some areas.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Help with moving.&lt;/strong&gt;
Some people might have to relocate to areas which allow them access to necessary care.
This is disruptive and expensive even in the best of times, let alone now!
Uprooting your life and leaving friends and family is scary—and people &lt;em&gt;will&lt;/em&gt; have to do it when legislatures pass restrictive laws.
They'll move to states (or countries) which have more access to the care they need.
This bucket includes: help with loading a truck, financial assistance with the move, or just time off for the move itself.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Benefits for their partners/families.&lt;/strong&gt;
Just as gender-affirming care is at risk, same-sex marriage is also in the sights for some.
The Supreme Court has signaled willingness to overturn it, and it could become back to states.
If that happens, families could lose their health care if their coverage is revoked.
So, we need our employers to reassure us that if that comes to pass, their benefits aren't going away, and the company will continue to cover them and their families.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access to lawyers for help with documents, to safeguard marriage rights.&lt;/strong&gt;
There is reason to think same-sex marriage may be targeted.
While some of that will be out of our control, we &lt;em&gt;can&lt;/em&gt; draft documents to put together the rights that typically come with marriage.
This includes wills (so your possessions pass to your spouse, usually a given from marriage), power of attorney, etc.
This &lt;a href="https://mastodon.world/@comicslibrarian/113445002025246014"&gt;helpful thread on mastodon&lt;/a&gt; has more details, but it is not cheap ($3,000 for the linked example).
If we lose this as a guarantee from the government, we'll need help with both the financial side of this and with figuring out the logistics of it all.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Visible, loud support.&lt;/strong&gt;
It is very important that all those who support our rights continue to do so in a visible way.
We need folks to continue posting, showing up at rallies, calling elected representatives.
We need folks to keep standing up for queer folks who are harassed in public, asking that bully, "hey, what's your problem?"
Visible, loud support helps us know that we're supported, and it helps our elected representatives &lt;em&gt;know&lt;/em&gt; that stripping our rights was &lt;em&gt;not the point&lt;/em&gt; of this election.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Being listened to and advocated for.&lt;/strong&gt;
I have had quite a few people reach out to me asking if I'm okay, and expressing concern for my safety now.
It is healing to know that so many people have enough love for me that they want to check in and make sure I'm okay.
This is something &lt;em&gt;everyone&lt;/em&gt; can do.
You can lend an ear to listen to concerns and worries, then go advocate for solutions in your community, state, country.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1 id="how-you-can-help"&gt;How you can help&lt;/h1&gt;
&lt;p&gt;What you can do directly to help depends a &lt;em&gt;lot&lt;/em&gt; on the position you're in.
We can each do something, and all of it counts.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Executive of a company.&lt;/strong&gt;
If you're in the top leadership of a company, then you have the most power here.
You probably cannot make &lt;em&gt;unilateral&lt;/em&gt; decisions—even the CEO reports to someone, the board.
But you have the most power of anyone at the company, and you can use this.&lt;/p&gt;
&lt;p&gt;Take this power and use it to put your values into action.
If you've told your employees you care about them, show them with concrete, decisive action to protect their rights here.
As an executive, you can either directly decide these issues, or you are peers with those who can.
And if they refuse to do it?
You can threaten to strike or walk away, even as a single individual, because as an executive you hold a large bargaining chip.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Manager.&lt;/strong&gt;
If you run a team, then you can probably do many things on this list.
You can't &lt;em&gt;directly&lt;/em&gt; give commitments for healthcare, but you &lt;em&gt;can&lt;/em&gt; pressure your top leadership to do so.
You can certainly cut your employees some slack, and let them know that if they need time to heal it's not going to be a problem in their performance reviews.
If you are involved in the budget process, then you may have some flexibility to allocate funds toward these expenses, too.&lt;/p&gt;
&lt;p&gt;Once again, if there's no action from top management on these, you can let them know that it's a deal-breaker for you.
That if the company doesn't respect its employees, you might not be among them.
If you have privilege, this is a powerful way to &lt;em&gt;wield&lt;/em&gt; it.
Why accumulate social capital if you're never willing to spend it for the most vulnerable?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Individual contributor.&lt;/strong&gt;
You can't effect change in the company directly.
But you can use two techniques very powerfully: advocacy and collective action.&lt;/p&gt;
&lt;p&gt;You can advocate to upper management.
This is very important to do, because they need to know about these issues in order to do anything about them.
And it's &lt;em&gt;very&lt;/em&gt; helpful to have someone do this who is &lt;em&gt;not&lt;/em&gt; part of the affected marginalized group, since it both shows that it's not just a "oh give me something" action and it also is a way of lessening the burden on an already overstressed group.&lt;/p&gt;
&lt;p&gt;And you can organize collective action.
I've done this a couple of times at work, and we produced small but real change.
Each time, the basic approach I took was: reflect on what I am concerned about and what we want; talk to people and see if they share my concerns; if they do, see if they'll sign a letter talking about our concerns; present the letter to leadership, with a critical mass.
If you're a &lt;em&gt;senior&lt;/em&gt; individual contributor, collective action should be right up your alley.
It leverages the skills you need to do engineering leadership: listening, managing up, and gathering support.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Friend.&lt;/strong&gt;
You can be a lifeline for your friends.
Reach out and see how they're doing. (To all those who've reached out to me: I love you, thank you.)
Advocate for them and support them, and be &lt;a href="https://www.youtube.com/watch?v=FmiwYAlVxDw"&gt;loud&lt;/a&gt; about it.&lt;/p&gt;
&lt;p&gt;Help them load the truck.
Find a lawyer for their document updates.
Do the research on where might be safe.&lt;/p&gt;
&lt;p&gt;Most of all, keep on loving them and holding them.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;There's probably more.
I &lt;em&gt;hope&lt;/em&gt; there's more that can be done.
But this is what I've got right now.&lt;/p&gt;
&lt;p&gt;If you're hurting right now, know that I am, too.
I'm tired, and scared, and &lt;em&gt;defiant&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;When FIDE, the international governing body of chess, tried to exclude trans women from women's chess, my response was simple.
I became active in my local chess club again, and I became a tournament director.
I became visible as a trans woman in chess.
That was a little different, since FIDE doesn't have any control over my life or my rights.&lt;/p&gt;
&lt;p&gt;But I live in a community that supports me, with a family that supports me.
I'm going to continue to live my best life to be good representation, if I can be.
I'm going to keep writing, and keep making art, and keep making music.
And I hope that together, we can get through this with as little harm as possible.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Thank you to two anonymous friends who gave me feedback on earlier drafts of this post.
I usually attribute people directly (after asking their permission), but this post is more sensitive than many, and I don't want anyone to be at risk from that.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 11 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/supporting-friends-and-coworkers-in-this-time/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>MTA-STS Preload</title><link>https://alexsci.com/blog/mta-sts-preload/</link><description>Tracking domains that support MTA-STS</description><author>Built on Shards of Silicon: Robert Alexander's Tech Blog</author><pubDate>Mon, 11 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://alexsci.com/blog/mta-sts-preload/</guid></item><item><title>Leveraging logprobs to build better generative AI systems</title><link>https://sophiabits.com/blog/leveraging-logprobs</link><description>&lt;p&gt;Autofilling form fields has become an increasingly common and practical application of generative AI. With just a bit of context and a well-crafted prompt, AI can produce reliable results for various input fields. One of the most appealing aspects of this use case is its simplicity and quick implementation—developers can often build and deploy this feature without extensive training data, sometimes in just a few hours.&lt;/p&gt;&lt;p&gt;Form field autofilling was one of the first things I ever built with GPT, and since then it’s been infectious throughout applications I’ve maintained. It’s a &lt;em&gt;really&lt;/em&gt; great feature to pitch if your exec wants to munge AI into your product, because it genuinely helps users save time while also requiring little investment. It’s unlikely you’ll need to embark on a dramatic data project to successfully ship it, and retrofitting a “✨” button on your form field to trigger it is usually straightforward.&lt;/p&gt;&lt;p&gt;In this post we’ll take a look at a very simple prompt for categorizing extracurricular activities like “chess club” or “soup kitchen,” based on their title. We’ll then take a look at a case where our simple prompt gives us weird output, and explore some metrics we can use to help our system monitor itself in production. By the end you’ll understand two common metrics for evaluating an LLM’s confidence in its output, and have a concrete idea of &lt;em&gt;how&lt;/em&gt; to use these metrics to build more reliable user-facing genAI systems.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Read more on &lt;a href="https://sophiabits.com/blog/leveraging-logprobs"&gt;sophiabits.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description><author>Sophia Willows' Blog</author><pubDate>Mon, 11 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://sophiabits.com/blog/leveraging-logprobs</guid></item><item><title>Yorkshires</title><link>https://sam.hooke.me/recipe/yorkshires/</link><description>&lt;h2 id="ingredients"&gt;Ingredients&lt;/h2&gt;
&lt;p&gt;Makes 8-12 yorkshires (or ~275ml):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;70g plain flour.&lt;/li&gt;
&lt;li&gt;2 eggs.&lt;/li&gt;
&lt;li&gt;100ml milk (both cow or oat milk work well, though oat may fizz a little when baking).&lt;/li&gt;
&lt;li&gt;Oil (avocado or sunflower).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Equipment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Muffin tray with 8-12 holes.&lt;/li&gt;
&lt;li&gt;Pouring jug.&lt;/li&gt;
&lt;li&gt;Whisk or fork.&lt;/li&gt;
&lt;li&gt;Plate with paper towel.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="method"&gt;Method&lt;/h2&gt;
&lt;h3 id="make-the-mixture"&gt;Make the mixture&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Pre-heat oven to 210°C fan (230°C without).&lt;/li&gt;
&lt;li&gt;Put flour in bowl, beat in eggs until smooth.&lt;/li&gt;
&lt;li&gt;Gradually mix in milk.&lt;/li&gt;
&lt;li&gt;Transfer the mixture into a pouring jug.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="heat-up-the-muffin-tray"&gt;Heat up the muffin tray&lt;/h3&gt;
&lt;ol start="5"&gt;
&lt;li&gt;Fill each hole in the muffin tray with just enough oil to fill the circle without needing to swish the tray about (typically half a tablespoon).&lt;/li&gt;
&lt;li&gt;Place the tray in the oven until the oil is very hot, about 5 minutes.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="pour-the-mixture"&gt;Pour the mixture&lt;/h3&gt;
&lt;ol start="7"&gt;
&lt;li&gt;Take the muffin tray with the hot oil out of the oven.&lt;/li&gt;
&lt;li&gt;Quickly and smoothly, in one continuous pouring motion, use the jug to fill each muffin tray hole. Aim for the center of each hole, so that the batter radiates out in all directions towards the edge of the circle. Some mixture will inevitably land on the tray as you move from one hole to the next, but this is okay because speed is of the essence.&lt;/li&gt;
&lt;li&gt;Once all the mixture is used (you may need to go back and top some up to even them all out), place the muffin tray back in the oven&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="bake"&gt;Bake&lt;/h3&gt;
&lt;ol start="10"&gt;
&lt;li&gt;Allow to bake without opening the door until risen and golden, 10-12 minutes.&lt;/li&gt;
&lt;li&gt;When done, remove from the oven, and transfer the yorkshires out of the tray onto a plate lined with paper towel, which will help soak any excess oil. The yorkshires should pop out without much trouble. If they are left to cool for too long, or the ratio of oil to batter was wrong, they may need help from utensil to be removed.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Ideally, use top shelf of oven. If using two trays across different shelves, take the top one out first and give the bottom one another minute or so.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;</description><author>Sam Hooke</author><pubDate>Sun, 10 Nov 2024 13:00:00 GMT</pubDate><guid isPermaLink="true">https://sam.hooke.me/recipe/yorkshires/</guid></item><item><title>Link to https://blog.kizu.dev/my-mastodon-starter-pack/</title><link>https://qubyte.codes/links/1731228825541</link><description>&lt;p&gt;A really great how-to article for getting started with Mastodon. &lt;a href="https://qubyte.codes/links/1731228825541"&gt;My Mastodon Starter Pack — Roma’s Unpolished Posts&lt;/a&gt;&lt;/p&gt;</description><author>Qubyte Codes</author><pubDate>Sun, 10 Nov 2024 10:53:45 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/links/1731228825541</guid></item><item><title>Purple Hibiscus</title><link>https://arunmani.in/library/purple-hibiscus/</link><description>A rich family may not mean a happy life.</description><author>Arun Mani</author><pubDate>Sun, 10 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://arunmani.in/library/purple-hibiscus/</guid></item><item><title>Reflections on the 2024 US Presidential Election</title><link>https://evanfields.net/2024-Election-Thoughts/</link><description>Warning: fully political post, which is not the typical genre here and not the vibe I’m mostly trying to cultivate. I pretty much stand by this, though my emotions have cooled in the weeks since the election.</description><author>Evan Fields</author><pubDate>Sun, 10 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://evanfields.net/2024-Election-Thoughts/</guid></item><item><title>Christmas in Notting Hill</title><link>https://mehulkar.com/blog/2024/11/christmas-in-notting-hill?utm_source=rss</link><description>&lt;div class="letterboxd-movie-data-content"&gt;
   &lt;p&gt;&lt;img src="https://a.ltrbxd.com/resized/film-poster/1/0/2/9/8/6/0/1029860-christmas-in-notting-hill-0-600-0-900-crop.jpg?v=2f7c2050f1" /&gt;&lt;/p&gt; &lt;p&gt;Bad. Very bad.&lt;/p&gt; 
  &lt;p&gt;Rated 0.5 stars.&lt;/p&gt;&lt;p&gt;
  &lt;/p&gt;&lt;div class="float-clear"&gt;&lt;/div&gt;
&lt;/div&gt;

        &lt;p&gt;Thanks for reading this post via RSS!&lt;/p&gt;</description><author>Mehul Kar's blog</author><pubDate>Sun, 10 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://mehulkar.com/blog/2024/11/christmas-in-notting-hill?utm_source=rss</guid></item><item><title>Why I Switched to Aerospace: Tiling Window Management Made Easy on macOS</title><link>https://konradkruk.com/blog/why-i-switched-to-aerospace-tiling-window-management-made-easy-on-macos</link><description>Discover how Aerospace, a tiling window manager for macOS, simplifies window management and boosts productivity. Learn about its powerful features, easy setup, and tips for mastering your workflow on macOS.</description><author>Konrad Kruk Blog</author><pubDate>Sun, 10 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://konradkruk.com/blog/why-i-switched-to-aerospace-tiling-window-management-made-easy-on-macos</guid></item><item><title>TAO - Meta's scalable architecture powering world's largest social graph</title><link>https://engineeringatscale.substack.com/p/tao-metas-scalable-architecture-powering</link><description>Meta (formerly Facebook) has more than 2 billion Daily Active Users (DAU).</description><author>Engineering At Scale</author><pubDate>Sat, 09 Nov 2024 12:56:37 GMT</pubDate><guid isPermaLink="true">https://engineeringatscale.substack.com/p/tao-metas-scalable-architecture-powering</guid></item><item><title>How to install Cursor AI IDE on Fedora Asahi Linux Arm64</title><link>https://themythicalengineer.com/how-to-install-cursor-ai-ide-on-fedora-asahi-linux-arm64.html</link><description/><author>The Mythical Engineer</author><pubDate>Sat, 09 Nov 2024 10:45:00 GMT</pubDate><guid isPermaLink="true">https://themythicalengineer.com/how-to-install-cursor-ai-ide-on-fedora-asahi-linux-arm64.html</guid></item><item><title>Building a Homelab, Part 5 - New Services and Hardening</title><link>https://blog.janissary.xyz/posts/homelab-5</link><author>janissary</author><pubDate>Sat, 09 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.janissary.xyz/posts/homelab-5</guid></item><item><title>Open source mac apps I love</title><link>https://muffinman.io/blog/open-source-mac-apps-i-love/</link><description>&lt;article class="article"&gt;
&lt;p&gt;I want to share some really nice open-source applications I use on a daily basis. I did a &lt;a href="/blog/small-mac-apps-i-love/"&gt;similar post&lt;/a&gt; about six years ago, so I felt there is a need for an updated one. I focused on the apps that I feel are really convenient but less known.&lt;/p&gt;
&lt;p&gt;All of the applications listed are free and open source. For each app, I included a brew install command.&lt;/p&gt;
&lt;p&gt;If you happen to like and use them, please consider donating to their respective authors.&lt;/p&gt;
&lt;p&gt;Here are all of the apps in alphabetical order:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;GUI Applications&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#hidden-bar"&gt;Hidden Bar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#imageoptim"&gt;ImageOption&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#itsycal"&gt;Itsycal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#karabiner-elements"&gt;Karabiner-Elements&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#keeping-you-awake"&gt;Keeping You Awake&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#maccy"&gt;Maccy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#maestral"&gt;Maestral&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#openinterminal"&gt;OpenInTerminal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#pearcleaner"&gt;Pearcleaner&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#pika"&gt;Pika&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#rectangle"&gt;Rectangle&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Bash programs&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#dust"&gt;dust&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#ripgrep"&gt;ripgrep&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#tio"&gt;tio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#zoxide"&gt;zoxide&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="gui-applications"&gt;GUI Applications &lt;a class="anchor-link" href="#gui-applications"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="hidden-bar"&gt;Hidden Bar &lt;a class="anchor-link" href="#hidden-bar"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/dwarvesf/hidden"&gt;GitHub&lt;/a&gt;
&lt;code&gt;brew install hiddenbar&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Hidden Bar allows you to hide icons from the menu bar. It seems that every app nowadays wants to be in the menu bar, and this helps declutter it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Hidden Bar settings" class="image" height="1186" src="./hidden-bar.png" width="1560" /&gt;&lt;/p&gt;
&lt;h3 id="imageoptim"&gt;ImageOptim &lt;a class="anchor-link" href="#imageoptim"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/ImageOptim/ImageOptim"&gt;GitHub&lt;/a&gt; | &lt;a href="https://imageoptim.com/mac"&gt;Website&lt;/a&gt;
&lt;code&gt;brew install imageoptim&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;As its name suggests, ImageOptim optimizes images. It is a UI for a bunch of lossless image optimization tools. Just drop your images and it will compress them without losing any quality.&lt;/p&gt;
&lt;p&gt;&lt;img alt="ImageOptim main window" class="image" height="1014" src="./imageoptim.png" width="1400" /&gt;&lt;/p&gt;
&lt;h3 id="itsycal"&gt;Itsycal &lt;a class="anchor-link" href="#itsycal"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/sfsam/Itsycal"&gt;GitHub&lt;/a&gt; | &lt;a href="https://www.mowglii.com/itsycal/"&gt;Website&lt;/a&gt;
&lt;code&gt;brew install itsycal&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A small calendar that lives in your menu bar. Why is this not built into the operating system?&lt;/p&gt;
&lt;p&gt;&lt;img alt="Istycal calendar" class="image" height="800" src="./itsycal.png" width="1250" /&gt;&lt;/p&gt;
&lt;h3 id="karabiner-elements"&gt;Karabiner-Elements &lt;a class="anchor-link" href="#karabiner-elements"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/pqrs-org/Karabiner-Elements"&gt;GitHub&lt;/a&gt; | &lt;a href="https://pqrs.org/osx/karabiner/"&gt;Website&lt;/a&gt; &lt;code&gt;brew install karabiner-elements&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;I just can&amp;#x27;t get used to Apple&amp;#x27;s international keyboard layout and the tilde being in the bottom left. Karabiner solves that. It&amp;#x27;s a keyboard customizer that allows you to remap keys and create complex modifications.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Karabiner main window" class="image" height="1640" src="./karabiner.png" width="2424" /&gt;&lt;/p&gt;
&lt;h3 id="keeping-you-awake"&gt;Keeping You Awake &lt;a class="anchor-link" href="#keeping-you-awake"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/newmarcel/KeepingYouAwake"&gt;GitHub&lt;/a&gt; | &lt;a href="https://keepingyouawake.app/"&gt;Website&lt;/a&gt;
&lt;code&gt;brew install keepingyouawake&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Prevents your Mac from going to sleep by clicking on its icon in the menu bar. I use it when I&amp;#x27;m running my pen plotter to make sure the laptop doesn&amp;#x27;t go to sleep.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Keeping You Awake" class="image" height="600" src="./keeping-you-awake.png" width="1250" /&gt;&lt;/p&gt;
&lt;h3 id="maccy"&gt;Maccy &lt;a class="anchor-link" href="#maccy"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/p0deje/Maccy"&gt;GitHub&lt;/a&gt; | &lt;a href="https://maccy.app/"&gt;Website&lt;/a&gt;
&lt;code&gt;brew install maccy&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A clipboard manager. It saves your clipboard history and allows you to quickly access it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Keeping You Awake" class="image" height="800" src="./maccy.png" width="1250" /&gt;&lt;/p&gt;
&lt;h3 id="maestral"&gt;Maestral &lt;a class="anchor-link" href="#maestral"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/samschott/maestral"&gt;GitHub&lt;/a&gt; | &lt;a href="https://maestral.app/"&gt;Website&lt;/a&gt;
&lt;code&gt;brew install maestral&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A lightweight Dropbox client. The official Dropbox app has become bloated over the years, and Maestral is a great alternative.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Maestral" class="image" height="940" src="./maestral.png" width="1250" /&gt;&lt;/p&gt;
&lt;h3 id="openinterminal"&gt;OpenInTerminal &lt;a class="anchor-link" href="#openinterminal"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/Ji4n1ng/OpenInTerminal"&gt;GitHub&lt;/a&gt;
&lt;code&gt;brew install openinterminal&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A Finder extension that allows you to open the current directory in a terminal or a code editor.&lt;/p&gt;
&lt;p&gt;&lt;img alt="OpenInTerminal Finder extension" class="image" height="980" src="./open-in-terminal.png" width="1450" /&gt;&lt;/p&gt;
&lt;h3 id="pearcleaner"&gt;Pearcleaner &lt;a class="anchor-link" href="#pearcleaner"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/alienator88/Pearcleaner"&gt;GitHub&lt;/a&gt; | &lt;a href="https://itsalin.com/appInfo/?id=pearcleaner"&gt;Website&lt;/a&gt; &lt;code&gt;brew install pearcleaner&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;An app remover that lets you select an app and finds all of its files, allowing you to remove it in a single click.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pearcleaner main window" class="image" height="1480" src="./pearcleaner.png" width="2024" /&gt;&lt;/p&gt;
&lt;h3 id="pika"&gt;Pika &lt;a class="anchor-link" href="#pika"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/superhighfives/pika"&gt;GitHub&lt;/a&gt; | &lt;a href="https://superhighfives.com/pika"&gt;Website&lt;/a&gt;
&lt;code&gt;brew install pika&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A system-wide color picker. I&amp;#x27;m &lt;em&gt;picky&lt;/em&gt; when it comes to these, and Pika works in the way I expect a color picker to work.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Pika window and eyedropper" class="image" height="800" src="./pika.png" width="1250" /&gt;&lt;/p&gt;
&lt;h3 id="rectangle"&gt;Rectangle &lt;a class="anchor-link" href="#rectangle"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/rxhanson/Rectangle"&gt;GitHub&lt;/a&gt; | &lt;a href="https://rectangleapp.com/"&gt;Website&lt;/a&gt;
&lt;code&gt;brew install rectangle&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A window tiling app and successor to Spectacle. Allows you to move and tile windows using keyboard shortcuts.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Rectangle settings window" class="image" height="958" src="./rectangle.png" width="1768" /&gt;&lt;/p&gt;
&lt;h2 id="bash"&gt;Bash &lt;a class="anchor-link" href="#bash"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You might also want to check out the &lt;a href="https://github.com/ibraheemdev/modern-unix"&gt;Modern Unix&lt;/a&gt; repository, which contains modern alternatives to many traditional Unix commands.&lt;/p&gt;
&lt;h3 id="dust"&gt;dust &lt;a class="anchor-link" href="#dust"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/bootandy/dust"&gt;GitHub&lt;/a&gt; &lt;code&gt;brew install dust&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A program that estimates file space usage in the current directory.&lt;/p&gt;
&lt;p&gt;&lt;img alt="dust running in terminal" class="image" height="1192" src="./dust.png" width="2012" /&gt;&lt;/p&gt;
&lt;h3 id="ripgrep"&gt;ripgrep &lt;a class="anchor-link" href="#ripgrep"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/BurntSushi/ripgrep"&gt;GitHub&lt;/a&gt; &lt;code&gt;brew install ripgrep&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Ripgrep recursively searches the current folder using regular expressions. I don&amp;#x27;t use it often, but when I need it, I&amp;#x27;m really thankful it exists.&lt;/p&gt;
&lt;h3 id="tio"&gt;tio &lt;a class="anchor-link" href="#tio"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/tio/tio"&gt;GitHub&lt;/a&gt; &lt;code&gt;brew install tio&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A serial device I/O tool, a replacement for the screen command. I&amp;#x27;m not using it&amp;#x27;s advanced features, but I loved the auto-reconnect feature while working on my &lt;a href="https://github.com/stanko/retro-frame"&gt;Retro Frame&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="zoxide"&gt;zoxide &lt;a class="anchor-link" href="#zoxide"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/ajeetdsouza/zoxide"&gt;GitHub&lt;/a&gt; &lt;code&gt;brew install zoxide&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;In my previous post, I mentioned that autojump is pure awesomeness. Well, zoxide is pretty much the same thing, but faster.&lt;/p&gt;
&lt;p&gt;Both let you jump around the system using fuzzy search, and prioritize the directories you visit more often. If I didn&amp;#x27;t explain it well, just check the gif on it&amp;#x27;s &lt;a href="https://github.com/ajeetdsouza/zoxide"&gt;GitHub page&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I hope you found a few you like!&lt;/p&gt;
&lt;/article&gt;</description><author>Muffin Man</author><pubDate>Sat, 09 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://muffinman.io/blog/open-source-mac-apps-i-love/</guid></item><item><title>Paris Christmas Waltz</title><link>https://mehulkar.com/blog/2024/11/paris-christmas-waltz?utm_source=rss</link><description>&lt;div class="letterboxd-movie-data-content"&gt;
   &lt;p&gt;&lt;img src="https://a.ltrbxd.com/resized/film-poster/1/0/1/6/6/1/1/1016611-paris-christmas-waltz-0-600-0-900-crop.jpg?v=a078ef8794" /&gt;&lt;/p&gt; &lt;p&gt;Watched on Saturday November 9, 2024.&lt;/p&gt; 
  &lt;p&gt;Rated 0.5 stars.&lt;/p&gt;&lt;p&gt;
  &lt;/p&gt;&lt;div class="float-clear"&gt;&lt;/div&gt;
&lt;/div&gt;

        &lt;p&gt;Thanks for reading this post via RSS!&lt;/p&gt;</description><author>Mehul Kar's blog</author><pubDate>Sat, 09 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://mehulkar.com/blog/2024/11/paris-christmas-waltz?utm_source=rss</guid></item><item><title>A Paris Christmas Waltz</title><link>https://mehulkar.com/blog/2024/11/a-paris-christmas-waltz?utm_source=rss</link><description>&lt;div class="letterboxd-movie-data-content"&gt;
   &lt;p&gt;&lt;img src="https://a.ltrbxd.com/resized/film-poster/1/0/1/6/6/1/1/1016611-paris-christmas-waltz-0-600-0-900-crop.jpg?v=a078ef8794" /&gt;&lt;/p&gt; &lt;p&gt;Watched on Saturday November 9, 2024.&lt;/p&gt; 
  &lt;p&gt;Rated 0.5 stars.&lt;/p&gt;&lt;p&gt;
  &lt;/p&gt;&lt;div class="float-clear"&gt;&lt;/div&gt;
&lt;/div&gt;

        &lt;p&gt;Thanks for reading this post via RSS!&lt;/p&gt;</description><author>Mehul Kar's blog</author><pubDate>Sat, 09 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://mehulkar.com/blog/2024/11/a-paris-christmas-waltz?utm_source=rss</guid></item><item><title>Pilgrimages to SF and the valley</title><link>prashanth.world/squeaky-nail/prashanth.world/pilgrimages-to-sf/</link><description>I was listening to the most recent episode of Acquired about the founding story of Facebook/Meta, where they discussed a trip that Mark Zuckerberg took to Silicon Valley during the Christmas break just before he started writing the first code for Facebook. David (the podcast host) describes it briefly as</description><author>Prashanth Sadasivan</author><pubDate>Sat, 09 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">prashanth.world/squeaky-nail/prashanth.world/pilgrimages-to-sf/</guid></item><item><title>Revert a commited secret from remote repository</title><link>https://themythicalengineer.com/revert-a-commited-secret-from-remote-repository.html</link><description>To remove the last commit that contains a secret from your remote repository (GitHub, Bitbucket, etc.) and push again, you can follow these steps:</description><author>The Mythical Engineer</author><pubDate>Fri, 08 Nov 2024 07:11:00 GMT</pubDate><guid isPermaLink="true">https://themythicalengineer.com/revert-a-commited-secret-from-remote-repository.html</guid></item><item><title>Setup a Production Redis Cluster</title><link>https://themythicalengineer.com/setup-production-redis-cluster.html</link><description/><author>The Mythical Engineer</author><pubDate>Fri, 08 Nov 2024 05:24:00 GMT</pubDate><guid isPermaLink="true">https://themythicalengineer.com/setup-production-redis-cluster.html</guid></item><item><title>Bacon for everything - a roadmap</title><link>https://dystroy.org/blog/bacon-everything-roadmap/</link><description>&lt;p&gt;You want to use bacon on non rust projects?
So do I.&lt;/p&gt;</description><author>dystroy|Canop / blog</author><pubDate>Fri, 08 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://dystroy.org/blog/bacon-everything-roadmap/</guid></item><item><title>Phoenix LiveView: Async Assign Pattern</title><link>https://blog.andyglassman.com/2023/06/phoenix-liveview-async-assign-pattern.html</link><description>&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhK49xMjyducCjCYo72GKZ66nEHpEPpeqi_CJyTHThkTU10NB0O-OzPX0SqW0qwW0r2OGs4k0PoFBWVY2eu-PTC5wEBcPx6GRe9wg5qHLcOMKCJO8bAtPQvyHSjGGOtSBzyaYY7wcUuojaHP9dZPBNWyR8mcSyTRwpCrEEoJ-J2Zk59d7PeK4Akag/s1024/trains.png" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhK49xMjyducCjCYo72GKZ66nEHpEPpeqi_CJyTHThkTU10NB0O-OzPX0SqW0qwW0r2OGs4k0PoFBWVY2eu-PTC5wEBcPx6GRe9wg5qHLcOMKCJO8bAtPQvyHSjGGOtSBzyaYY7wcUuojaHP9dZPBNWyR8mcSyTRwpCrEEoJ-J2Zk59d7PeK4Akag/s320/trains.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;UPDATE: LiveView has added a built in, more robust way of using this pattern.&amp;nbsp; &lt;a href="https://hexdocs.pm/phoenix_live_view/Phoenix.LiveView.html#module-async-operations"&gt;Check it out here&lt;/a&gt;!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;I've been using LiveView for about two years now.  It's a great framework that makes snappy and responsive pages.  One anti-pattern I see fairly often is loading a lot of data in the initial page render.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;For the un-initiated, the &lt;span style="font-family: courier;"&gt;mount/3&lt;/span&gt; function is called twice.&amp;nbsp; Once for the initial 'dead' render, and again after the socket is connected.&amp;nbsp; &amp;nbsp;Many times, for the sake of simple straight forward code, not much is done differently between these two renders.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I haven't found any references (I'm sure they exist) on a best practice for managing the following flow:&lt;/div&gt;&lt;div&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Set sensible, lightweight default values on initial render.&lt;/li&gt;&lt;li&gt;Kick off one or more async processes to make longer running function calls.&lt;/li&gt;&lt;li&gt;Receive the values in the LiveView, and update the assigns.&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;As always in Elixir, the tools are powerful, and theres many ways to accomplish this.&lt;/div&gt;&lt;div&gt;&lt;ul style="text-align: left;"&gt;&lt;li&gt;Spawn a linked, or unlinked process.&lt;/li&gt;&lt;li&gt;Start a supervised, or unsupervised Task.&lt;/li&gt;&lt;li&gt;Make a call to a GenServer, and have it send a message back.&lt;/li&gt;&lt;li&gt;Publish to a PubSub topic, and listen for a response.&lt;/li&gt;&lt;/ul&gt;
  
  &lt;p&gt;
  Example, setting a load state, and then loading the actual data after socket is connected.
  &lt;/p&gt;  
&lt;pre&gt;&lt;code&gt;
defmodule NewsSite.NewsLive do
    
    def mount(_params, _session, socket) do
       
       socket = if !is_connected?(socket) do
       	assign(socket, :front_page, :loading)
       else
       	assign(socket, :front_page, News.front_page())
       end
       
       {:ok, socket}
    end
    
end    
&lt;/code&gt;&lt;/pre&gt;
  
  &lt;p&gt;
  Example, loading fast data on initial render, then slower calls async.
  &lt;/p&gt;  
&lt;pre&gt;&lt;code&gt;
defmodule NewsSite.NewsLive do
    
    def mount(_params, _session, socket) do
       
       socket = if !is_connected?(socket) do
       	assign(socket, :front_page, :loading)
       else
       	pid = self()
       	Task.start(fn -&amp;gt;
        	send(pid, {:front_page, News.front_page())
        end)
       end
       
       {:ok, socket}
    end
    
    @impl true
    def handle_info({:front_page, news_items}, socket) do
      {:noreply, assign(socket, :front_page, news_items)}
    end
    
end    
&lt;/code&gt;&lt;/pre&gt;
  
  &lt;div&gt;Each of these approaches, have their appropriate use case.&amp;nbsp; For the majority of the cases, in most apps, spawning a child process will suffice.&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The problem arises when you move past one async assign.&amp;nbsp; &amp;nbsp;Your mount function will start to get pretty messy, and you'll start breaking things out into private functions.&amp;nbsp; You also must try to stick to some sort of pattern across your LiveViews, otherwise each LiveView will work a bit differently.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;So, how did I solve this problem?&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h3 style="text-align: left;"&gt;The Async Assigns Module&lt;/h3&gt;&lt;div&gt;To solve this issue, I've encapsulated this basic pattern into an AsyncAssigns module.&amp;nbsp; &amp;nbsp;&lt;/div&gt;&lt;h4 style="text-align: left;"&gt;Functionality&lt;/h4&gt;&lt;div&gt;&lt;ul style="text-align: left;"&gt;&lt;li&gt;Allows you to set default data in the initial render.&lt;/li&gt;&lt;li&gt;Spawn a linked, or unlinked process that will do what is needed to fetch data.&lt;/li&gt;&lt;li&gt;Send a message back to the parent LiveView, and assign the values.&lt;/li&gt;&lt;/ul&gt;&lt;h4 style="text-align: left;"&gt;The Benefits&lt;/h4&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;Provides a consistent pattern for asynchronously loading assigns.&lt;/li&gt;&lt;li&gt;Provides a consistent pattern for setting defaults.&lt;/li&gt;&lt;li&gt;Allows assigns that must be loaded together to stay together.&amp;nbsp; Those that are not dependent can be loaded in parallel.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;



  &lt;p&gt;
  As an example, let's take a look at a News site.  Let's assume there is a &lt;span style="font-family: courier;"&gt;News&lt;/span&gt; module.
  &lt;/p&gt;  
  
&lt;pre&gt;&lt;code&gt;
defmodule News do
    
    @spec front_page() :: [News.t()] :: {:error, any()}
    def front_page() do
    	# Fast loading news that everyone gets.
    end
    
    @spec news_for_me(user :: User.t()) :: [News.t()] :: {:error, any()}
    def news_for_me(user) do
    	# Slower loading news based on my preferences.
    end
    
end    
&lt;/code&gt;&lt;/pre&gt;
  
  &lt;p&gt;
The `&lt;span style="font-family: courier;"&gt;mount/3`&lt;/span&gt; function for this site might look like this.  We are assuming the user is already authenticated, and is in `&lt;span style="font-family: courier;"&gt;assigns.user&lt;/span&gt;`.&amp;nbsp; This code will:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Initial call to mount (socket not connected).&lt;/li&gt;&lt;ol&gt;&lt;li&gt;Block the initial render until the `&lt;span style="font-family: courier;"&gt;front_page/0`&lt;/span&gt; data is returned, and assigned to the socket.&lt;/li&gt;&lt;/ol&gt;&lt;li&gt;Second call to mount (socket is connected).&lt;/li&gt;&lt;ol&gt;&lt;li&gt;Spawn an unlinked process that will run `&lt;span style="font-family: courier;"&gt;supplier`&lt;/span&gt;.&lt;/li&gt;&lt;li&gt;When `&lt;span style="font-family: courier;"&gt;news_for_me`&lt;/span&gt; returns, the spawned process will send a message back to the LiveView, and it will be assigned to &lt;span style="font-family: courier;"&gt;key&lt;/span&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;
  
&lt;pre&gt;&lt;code&gt;
defmodule NewsSite.NewsLive do
    
    def mount(_params, _session, socket) do
       
       socket = async_assign(
       	socket,
        key: :front_page,
        default: News.front_page(),
        supplier: fn socket -&amp;gt;
        	News.news_for_me(socket.assigns.user)
        end
       )
       
       {:ok, socket}
    end
    
end    
&lt;/code&gt;&lt;/pre&gt;  
  
  &lt;p&gt;
  Again, this is a more trivial example, and you may be wondering why we'd bother doing this, and not just check if the socket is connected in `&lt;span style="font-family: courier;"&gt;mount/3`&lt;/span&gt;, and load custom news there.  The pattern becomes much more useful the more things you are loading.  Let's expand on the first example. Assume that the code above has been moved to `&lt;span style="font-family: courier;"&gt;async_load_front_page/1`&lt;/span&gt;.
  &lt;/p&gt;
  
  &lt;pre&gt;&lt;code&gt;
defmodule NewsSite.NewsLive do
    
    def mount(_params, _session, socket) do
       
       socket = socket
       |&amp;gt; async_load_front_page()
       |&amp;gt; async_load_friends()
       |&amp;gt; async_load_suggested_articles()
       |&amp;gt; async_load_notifications()
       
       {:ok, socket}
    end
    
    # ...
    
    defp async_load_notifications(socket) do
      async_assign(
        socket,
        default: [notif_count: :loading, notif_unread: :loading],
        on_error: [notif_count: :error, notif_unread: :error],
        supplier: fn socket -&amp;gt;
            %{unread: unread, count: count} = Notifications.unread(socket.assigns.user)
            [notif_count: count, notif_unread: unread]
        end
      )
    end
    
end    
&lt;/code&gt;&lt;/pre&gt;  
  
  &lt;p&gt;
   Using this pattern, it is much cleaner and faster to implement many things that should load concurrently. In addition, you can see a slightly different use case
    in the&lt;span style="font-family: courier;"&gt;&amp;nbsp;`async_load_notifications/1`&lt;/span&gt; example.  The `:&lt;span style="font-family: courier;"&gt;notif_count`&lt;/span&gt;, and `:&lt;span style="font-family: courier;"&gt;notif_unread`&lt;/span&gt; assigns should be set at the same time.  If `&lt;span style="font-family: courier;"&gt;key&lt;/span&gt;` is not provided, then you can
    return any keyword list of assigns.  This helps ensure that assigns that are tied together are always set together.
    
    Also, in this setup, we are giving the caller of `&lt;span style="font-family: courier;"&gt;async_load_notifications&lt;/span&gt;` a choice on defaults.  If not specified, both `:&lt;span style="font-family: courier;"&gt;notif_count`&lt;/span&gt; and `&lt;span style="font-family: courier;"&gt;:notif_unread&lt;/span&gt;` will be set to 		loading.  If they only want to check for new notifications, only the count will be set to loading. This way, the rendered list of notifications will not go away, 			will still be updated when the results are returned.
    
    This is a contrived example, so don't pick it apart too much.  It's just intended to illustrate the flexibility of the pattern.
  &lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Another benefit of structuring your data loading like this, is that is very easy to reload values.&amp;nbsp; Below, you can see two events, one reloads notifications, and one only checks for new notifications.&amp;nbsp; This helps us manage assigns that should be modified together in one spot.&lt;/p&gt;
  
  
    &lt;pre&gt;&lt;code&gt;
defmodule NewsSite.NewsLive do
    
    @impl true
    def handle_event("reload_notifications", _unsigned_params, socket) do
      {:noreply, async_load_notifications(socket)}
    end
    
    @impl true
    def handle_event("check_for_new_notifications", _unsigned_params, socket) do
      {:noreply, async_load_notifications(socket, [notif_count: :loading])}
    end
    
    # ...
    
    defp async_load_notifications(socket, default \\ [notif_count: :loading, notif_unread: :loading]) do
      async_assign(
        socket,
        default: default,
        on_error: [notif_count: :error, notif_unread: :error],
        supplier: fn socket -&amp;gt;
            %{unread: unread, count: count} = if Notifications.unread(socket.assigns.user)
            [notif_count: count, notif_unread: unread]
        end
      )
    end
    
end    
&lt;/code&gt;&lt;/pre&gt;  
  &lt;p&gt;
  If you'd like to use this code, feel free to copy and reuse from the gist below.  I feel it is premature to turn this into a library, but maybe if I evolve it, and test
  it enough, I will publish it to hex.
  &lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Usage&lt;/h3&gt;
  
    
&lt;pre&gt;&lt;code&gt;

# Add the following to you Web.live_view/1 function so it's available to all LiveViews,
# or add directly to any Live you want to try it in.

use AsyncAssigns
import AsyncAssigns

&lt;/code&gt;&lt;/pre&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;  
  
  &lt;/div&gt;&lt;h2 style="text-align: left;"&gt;Update!&lt;/h2&gt;&lt;div&gt;This post received a good amount of views, and people have pointed out that it is a much better option to kick off these async processes with Task.&amp;nbsp; I actually was using this, but thought I'd experiment with using spwan / spawn_link directly.&amp;nbsp; I'll have to experiment with how these approaches differ, especially in error / crash situations.&amp;nbsp; If you plan to use the code below, you may want to swap out the spawns for Task calls!&amp;nbsp; (Thanks for calling this out &lt;a href="https://twitter.com/peregrine"&gt;Jason&lt;/a&gt;!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Also, I'm using live view version 17.x, and some of the functions have moved around for newer versions.&amp;nbsp; You may need to change where assigns, or is_connected? are being imported from.&amp;nbsp; Thanks for reading!&amp;nbsp; - Andy&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</description><author>Milwaukee Maven</author><pubDate>Thu, 07 Nov 2024 23:31:32 GMT</pubDate><guid isPermaLink="true">https://blog.andyglassman.com/2023/06/phoenix-liveview-async-assign-pattern.html</guid></item><item><title>Takeover Texas - A live exploration game</title><link>https://karmanyaah.malhotra.cc/projects/2024/11/hacktx/</link><description>In 24 hours, we made a game playable across the UT campus with live location tracking and a topographic map as a “situation room” display.</description><author>Karmanyaah Malhotra</author><pubDate>Thu, 07 Nov 2024 22:47:08 GMT</pubDate><guid isPermaLink="true">https://karmanyaah.malhotra.cc/projects/2024/11/hacktx/</guid></item><item><title>HackTX - Auto-aiming Turret</title><link>https://karmanyaah.malhotra.cc/projects/2023/10/hacktx/</link><description>In 24 hours, we made a crazy auto-aiming turret that shoots Orbeez at A&amp;amp;M students, and won HackTX 2023!</description><author>Karmanyaah Malhotra</author><pubDate>Thu, 07 Nov 2024 22:38:33 GMT</pubDate><guid isPermaLink="true">https://karmanyaah.malhotra.cc/projects/2023/10/hacktx/</guid></item><item><title>THE BOX - Hackathon Time Capsule</title><link>https://karmanyaah.malhotra.cc/projects/2024/11/thehackbox/</link><description>I made a hackathon time capsule that’s been going to hackathons accross the country for over a year sharing memories!</description><author>Karmanyaah Malhotra</author><pubDate>Thu, 07 Nov 2024 22:22:28 GMT</pubDate><guid isPermaLink="true">https://karmanyaah.malhotra.cc/projects/2024/11/thehackbox/</guid></item><item><title>Google Banned Me From Google Voice</title><link>https://www.dannyguo.com/blog/google-banned-me-from-google-voice</link><author>Danny Guo</author><pubDate>Thu, 07 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.dannyguo.com/blog/google-banned-me-from-google-voice</guid></item><item><title>on-run commands on file event</title><link>https://evilcookie.de/on-run-commands-on-file-event.html</link><description/><author>blog</author><pubDate>Wed, 06 Nov 2024 22:04:39 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/on-run-commands-on-file-event.html</guid></item><item><title>User Agents Hitting My Site</title><link>https://paul.kinlan.me/user-agents-hitting-my-site/</link><description>Curious about who's visiting my site, I built a user-agent tracker using Vercel middleware and KV storage.  It logs every request and displays a live table of user agents and hit counts, refreshing every minute. Check out the code on GitHub!</description><author>Modern Web Development with Chrome</author><pubDate>Wed, 06 Nov 2024 18:54:00 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/user-agents-hitting-my-site/</guid></item><item><title>Countdown timer</title><link>https://paul.kinlan.me/projects/countdown-timer/</link><description>I created a simple countdown timer web app that lets you track time until important events.  It's built with a focus on no-code using Replit, including a cool integration with Black Forrest Labs' image API via Replit's Agent feature. Check out the live app and source code!</description><author>Modern Web Development with Chrome</author><pubDate>Wed, 06 Nov 2024 12:46:00 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/projects/countdown-timer/</guid></item><item><title>Testing the Silk Platform in 2024: Achieving 20 GiB/s I/O Throughput in a Single Cloud VM</title><link>https://tanelpoder.com/posts/testing-the-silk-platform-in-2024/</link><description>&lt;p&gt;&lt;em&gt;Hands-on technical analysis of a novel data platform for high-performance block I/O in the cloud&lt;/em&gt;,
&lt;br /&gt;
&lt;em&gt;tested by &lt;a href="https://tanelpoder.com/about/"&gt;Tanel Poder&lt;/a&gt;, a database consultant and a long-time computer performance nerd.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="index"&gt;Index&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="#background-and-motivation"&gt;Background and motivation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#revisiting-the-silk-data-platform-scalable-architecture-by-design"&gt;Scalable Architecture by Design&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#enterprise-features"&gt;Enterprise Features&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#testing"&gt;Testing Results&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#io-latency"&gt;I/O Latency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#what-about-the-maximum-small-io-performance"&gt;1.3 Million IOPS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#any-surprises-issues-lessons-learned"&gt;Lessons Learned&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#summary-what-does-this-mean"&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="background-and-motivation"&gt;Background and Motivation&lt;/h3&gt;
&lt;p&gt;Back in 2021, my old friend Chris Buckel (@flashdba) asked me if I would like to test out the Silk Data Platform in the cloud and see how far I could push it. And then &lt;a href="https://tanelpoder.com/posts/testing-the-silk-platform/"&gt;write about it&lt;/a&gt;. We got pretty nice results, 5 GiB/s IO rate to the Silk backend just from a single cloud VM.&lt;/p&gt;</description><author>Tanel Poder Blog</author><pubDate>Wed, 06 Nov 2024 02:32:23 GMT</pubDate><guid isPermaLink="true">https://tanelpoder.com/posts/testing-the-silk-platform-in-2024/</guid></item><item><title>Embedding Collapse in Recommender Systems: Causes, Consequences, and Solutions</title><link>https://blog.reachsumit.com/posts/2024/11/embedding-collapse-recsys/</link><description>&lt;div class="featured-image"&gt;
                &lt;img src="/posts/2024/11/embedding-collapse-recsys/featured-image-preview.webp" /&gt;
            &lt;/div&gt;Learned embeddings often suffer from &amp;rsquo;embedding collapse&amp;rsquo;, where they occupy only a small subspace of the available dimensions. This article explores the causes of embedding collapse, from two-tower models to GNN-based systems, and its impact on model scalability and recommendation quality. We discuss methods to detect collapse and examine recent solutions proposed by research teams at Visa, Facebook AI, and Tencent Ads to address this challenge.</description><author>Sumit's Diary</author><pubDate>Wed, 06 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.reachsumit.com/posts/2024/11/embedding-collapse-recsys/</guid></item><item><title>Vi, Vim and NeoVim</title><link>https://crocidb.com/post/vi-vim-nvim/</link><description>&lt;p&gt;When I first heard of &lt;strong&gt;Vi&lt;/strong&gt;, it was actually &lt;strong&gt;Vim&lt;/strong&gt;. But people liked to call it &lt;em&gt;V-I&lt;/em&gt;, so it was common for people to add aliases to call it &lt;em&gt;vi&lt;/em&gt;: &lt;code&gt;alias vi=vim&lt;/code&gt; was a common line in people&amp;rsquo;s shell profiles.&lt;/p&gt;
&lt;p&gt;After the current &lt;strong&gt;NeoVim&lt;/strong&gt; craze, with lots of influencer creating content on it and tons of new users (&lt;a href="https://crocidb.com/post/from-ides-to-the-terminal"&gt;myself included&lt;/a&gt;), I see the interesting effect of people creating aliases to invoke &lt;strong&gt;NeoVim&lt;/strong&gt; with &lt;code&gt;vim&lt;/code&gt;. &lt;code&gt;alias vim=nvim&lt;/code&gt;, (myself incldued).&lt;/p&gt;
&lt;p&gt;I wonder when the next big editor comes, in a decade or so, let&amp;rsquo;s call it &lt;strong&gt;UltraNeoVim&lt;/strong&gt;, &lt;code&gt;unvim&lt;/code&gt;, people will start invoking it with &lt;code&gt;nvim&lt;/code&gt;. &lt;code&gt;alias nvim=unvim&lt;/code&gt;.&lt;/p&gt;</description><author>Bruno Croci</author><pubDate>Wed, 06 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://crocidb.com/post/vi-vim-nvim/</guid></item><item><title>Exploring Postgres's arena allocator by writing an HTTP server from scratch</title><link>http://notes.eatonphil.com/2024-11-06-exploring-postgress-arena-allocator-writing-http-server-scratch.html</link><description>&lt;p&gt;This is an external post of mine. Click
&lt;a href="https://www.enterprisedb.com/blog/exploring-postgress-arena-allocator-writing-http-server-scratch"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Wed, 06 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-11-06-exploring-postgress-arena-allocator-writing-http-server-scratch.html</guid></item><item><title>Support me</title><link>https://seirdy.one/support/</link><description>Support my work by sending me donations! This helps me continue Fediverse moderation, blogging, and coding.</description><author>All content on Seirdy’s Home</author><pubDate>Tue, 05 Nov 2024 21:47:21 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/support/</guid></item><item><title>libobscura</title><link>https://dorotac.eu/posts/libobscura/</link><description>&lt;h1&gt;Announcing libobscura&lt;/h1&gt;
&lt;p&gt;Let me formally introduce you to my most recent overly-ambitious experiment: &lt;em&gt;&lt;a href="https://libobscura.codeberg.page"&gt;libobscura&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="libobscura" src="libobscura.svg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Libobscura is a friendly library to use cameras on Linux.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At least that's the goal.&lt;/p&gt;
&lt;h2&gt;What does "friendly" mean?&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;It's hard to use it wrong.&lt;/em&gt; No segfaults. Errors guide you to the right track.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Point-and-shoot.&lt;/em&gt; If that's all you need, you get a RGB buffer in ten lines of code.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;It's easy to add support for new devices.&lt;/em&gt; Great documentation and a good internal API are the goals.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;It's easy to contribute to.&lt;/em&gt; Send patches using the &lt;a href="https://codeberg.org/libobscura/libobscura/pulls"&gt;web interface&lt;/a&gt;, not a mailing list.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TL;DR: with the simple buffer API you can get a frame in 6 calls, and map it to CPU in another 2:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;let cameras_list = vidi::actors::camera_list::spawn()?;
let cameras = cameras_list.cameras();
let camera = cameras_list.create(&amp;amp;cameras[0].info.id)
    .expect("No such camera")
    .expect("Failed to create camera");

let mut camera = camera.acquire();
if let Ok(ref mut camera) = camera {
    let mut stream = camera.start(
        Config{fourcc: FourCC::new(b"YUYV")},
        4
    ).unwrap();
    loop {
        let (buf, meta, _next) = stream.next().unwrap();
        let mmap = buf.memory_map_ro().unwrap();
        let data = mmap.as_slice();
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Go to project &lt;a href="https://codeberg.org/libobscura/libobscura/src/branch/master/crates/vidi-examples"&gt;examples&lt;/a&gt; for more.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A baby placing a missing block. They are stacked in the Bayer pattern." src="baby.png" /&gt;&lt;/p&gt;
&lt;p&gt;Figure 1: Libobscura will never be friendly enough for every audience.&lt;/p&gt;
&lt;h2&gt;What cameras?&lt;/h2&gt;
&lt;p&gt;Any webcams, industrial cameras, image sensors working exposed by the V4L2 interface are currently in scope.&lt;/p&gt;
&lt;h2&gt;What does "experiment" mean?&lt;/h2&gt;
&lt;p&gt;There are already other libraries for camera support on Linux. You can use the &lt;a href="https://www.kernel.org/doc/html/latest/userspace-api/media/v4l/v4l2.html"&gt;V4L2 APIs&lt;/a&gt; directly, or use &lt;a href="https://libcamera.org/"&gt;libcamera&lt;/a&gt;, or &lt;a href="https://gitlab.com/megapixels-org/libmegapixels"&gt;libmegapixels&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;They all strike various middle points on the power vs user-friendliness scale. Having worked with all of them while developing camera support for the &lt;a href="https://puri.sm/products/librem-5/"&gt;Librem 5&lt;/a&gt;, I never got the impression that any of them are particularly easy to use.&lt;/p&gt;
&lt;p&gt;Libobscura is an experiment because it tries to find an API that fulfills the needs of most people and remains hard to use wrong.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A diagram showing two axes: vertically ease of use and horizontally covered use cases. A couple of projects are positioned below an area called &amp;quot;unexplored&amp;quot;, whichis itself under am area called &amp;quot;unachievable&amp;quot;. The borders are roughly moving to less easy to use as covered use cases grow." src="diagram.png" /&gt;&lt;/p&gt;
&lt;p&gt;Figure 2: My "Perfect tool" conjecture. Imagine you have a perfectly well useable tool covering some of your needs. If your needs grow, the perfect tool covering those tools cannot be easier to use than your old tool. And humans are imperfect. This applies to APIs, as well. I put my rough idea of where I think a couple of examples fall. Libcamera tries to be as general as possible, so it's on the far right. OpenCV has Python bindings, so it's up top. Libobscura starts out a bit to the left and top, and I hope to push it to the right, to explore how many use cases can be added while staying easy.&lt;/p&gt;
&lt;p&gt;Perhaps it's impossible to improve on what current libraries do. But now libobscura is a space where it's possible to try out radical changes without bothering the maintainers of existing libraries – for example, to try out an entirely new approach.&lt;/p&gt;
&lt;h2&gt;Radical approaches&lt;/h2&gt;
&lt;p&gt;There are a couple of radical approaches in libobscura that earn it the "experiment" status.&lt;/p&gt;
&lt;h3&gt;Rust&lt;/h3&gt;
&lt;p&gt;Rust is a memory-safe systems language. Libobscura uses it to ensure that the APIs are hard to use wrong. A Rust API built with a little care does not let you crash with a segfault – the compiler will alert you before you can create problems.&lt;/p&gt;
&lt;p&gt;The Linux kernel started its &lt;a href="https://rust-for-linux.com/"&gt;own Rust experiment&lt;/a&gt; in 2020. Linux is a low-level project. Camera support is a low-level topic (many people working on libcamera also work on the kernel). If Rust is interesting for Linux, then it's interesting for libobscura.&lt;/p&gt;
&lt;h3&gt;Get RGB data&lt;/h3&gt;
&lt;p&gt;A camera library can't be described as easy to use if the pictures it gives you need to be processed before displaying.&lt;/p&gt;
&lt;p&gt;The typical format to display data is RGB while cameras return either Bayer or YUV data. With libobscura, the goal is to provide those conversions transparently, without any extra effort on behalf of the user or camera backend.&lt;/p&gt;
&lt;h3&gt;GPU acceleration&lt;/h3&gt;
&lt;p&gt;The conversions might not be implemented in hardware, but don't worry, libobscura has your back! It comes with a GPU-accelerated image processing library called &lt;a href="https://codeberg.org/libobscura/libobscura/src/branch/master/crates/crispy"&gt;crispy-img&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Why GPU? The idea started with the Librem 5, which will often record video while running on battery. Offloading this work to the GPU is a clear win, and I expect most Linux video-capable devices to have some form of GPU. And we're open to a CPU decoder if you want to contribute!&lt;/p&gt;
&lt;p&gt;&lt;img alt="A painter looking at a canvas with 4 colored strokes: 2 colored, 2 gray, representing YUYV. The painter holds a brush at a canvas on the other side with 3 colored strokes: red, green, and blue." src="painter.png" /&gt;&lt;/p&gt;
&lt;p&gt;Figure 3: An artist's impression of a pixel format conversion shader.&lt;/p&gt;
&lt;h2&gt;Status&lt;/h2&gt;
&lt;p&gt;Because libobscura is only two months old as a funded project, the current status is "proof of concept". There's a safe and limited user API, another more tricky but zero-copy API, the GPU accelerated library can handle some conversions, and camera support is relatively simple. USB camera demo works.&lt;/p&gt;
&lt;p&gt;But there are still &lt;a href="https://codeberg.org/libobscura/libobscura/issues"&gt;goals to achieve&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://codeberg.org/libobscura/libobscura/issues/1"&gt;change controls&lt;/a&gt; like brightness or focus,&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codeberg.org/libobscura/libobscura/issues/2"&gt;transparently integrate&lt;/a&gt; GPU processing,&lt;/li&gt;
&lt;li&gt;have a way to choose your &lt;a href="https://codeberg.org/libobscura/libobscura/issues/3"&gt;preferred output format&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;implement &lt;a href="https://codeberg.org/libobscura/libobscura/issues/5"&gt;auto-* algorithms&lt;/a&gt; when the camera doesn't have auto mode,&lt;/li&gt;
&lt;li&gt;replace &lt;a href="https://codeberg.org/libobscura/libobscura/issues/6"&gt;LGPL-licensed pieces&lt;/a&gt; from libvidi and crispycam (not great for link-time-optimized code)&lt;/li&gt;
&lt;li&gt;add &lt;a href="https://pipewire.org/"&gt;Pipewire&lt;/a&gt; &lt;a href="https://codeberg.org/libobscura/libobscura/issues/7"&gt;integration&lt;/a&gt;, so your browser can just pick up your camera feed for teleconferencing,&lt;/li&gt;
&lt;li&gt;add &lt;a href="https://codeberg.org/libobscura/libobscura/issues/9"&gt;Librem 5 support&lt;/a&gt; as a realistic, useful verification of concept.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Those will be the next steps for the project. First implement all the functionality and then extend support for more devices. This way we can catch corner cases in the API that are bound to appear with unusual setups.&lt;/p&gt;
&lt;h2&gt;The future is YOU&lt;/h2&gt;
&lt;p&gt;I'm making libobscura in the hope that it will be useful to people. As a base to build software, or to ship devices, or to learn what software architectures fit this problem.&lt;/p&gt;
&lt;p&gt;When I sent my funding request to Prototype Fund, I didn't expect to be taken seriously. After all, what motivated me most was that it was a cool challenge. Apparently they believed in me more than I did, because I got funding until March 2025.&lt;/p&gt;
&lt;p&gt;What happens until then and what happens next depends on how useful this work actually is to YOU. The ultimate goal of any software is to be useful, otherwise what's the point?&lt;/p&gt;
&lt;p&gt;So I invite YOU to analyze it, try it out, give feedback, experiment with it, copy the concepts, &lt;a href="https://libobscura.codeberg.page/community.html#contributing"&gt;contribute&lt;/a&gt; to docs, illustrations, and code, fork it entirely! Regardless if you come from the &lt;a href="https://postmarketos.org/"&gt;Mobile&lt;/a&gt; &lt;a href="https://mobian.org/"&gt;Linux&lt;/a&gt; &lt;a href="https://manjaro.org/"&gt;community&lt;/a&gt;, or &lt;a href="https://www.raspberrypi.com/documentation/accessories/camera.html"&gt;Raspberry Pi&lt;/a&gt;, or you have a laptop with this &lt;a href="https://github.com/linux-surface/linux-surface/wiki/Camera-Support"&gt;IPU thing&lt;/a&gt;, or if you're from libcamera. When you improve, libobscura fulfills its goals.&lt;/p&gt;
&lt;p&gt;So see you on the &lt;a href="https://codeberg.org/libobscura/libobscura"&gt;project page&lt;/a&gt;!&lt;/p&gt;
&lt;h2&gt;Thanks&lt;/h2&gt;
&lt;p&gt;Thank you to &lt;a href="https://puri.sm"&gt;Purism&lt;/a&gt;, for inviting me to the Linux camera work and funding the base which I'm using now.&lt;/p&gt;
&lt;p&gt;Thank you to all the libcamera people! I stol... was inspired by libcamera's architecture, and you also answered my countless questions both during Librem 5 development and recently.&lt;/p&gt;
&lt;p&gt;Thank you &lt;a href="https://blog.brixit.nl/"&gt;Martijn Braam&lt;/a&gt; for showing how to create simple config files for many mobile phones.&lt;/p&gt;
&lt;p&gt;Thank you &lt;a href="https://www.bmbf.de"&gt;BMBF&lt;/a&gt; for providing the funding money, and thanks &lt;a href="https://prototypefund.de/"&gt;Prototype Fund&lt;/a&gt; for connecting me to it :)&lt;/p&gt;
&lt;p&gt;&lt;img alt="Logo: sponsored by the Federal Ministry of Education and Research" src="bmbf.jpg" /&gt;&lt;/p&gt;</description><author>dcz's posts</author><pubDate>Tue, 05 Nov 2024 16:00:00 GMT</pubDate><guid isPermaLink="true">https://dorotac.eu/posts/libobscura/</guid></item><item><title>IPv6 in your home</title><link>https://dorotac.eu/posts/ipv6_homenet/</link><description>&lt;h1&gt;IPv6 in your home&lt;/h1&gt;
&lt;p&gt;IPv4, IPv6, who cares? As long as it works, right?&lt;/p&gt;
&lt;h2&gt;The death of IPv4 is greatly exaggerated&lt;/h2&gt;
&lt;p&gt;We ran out of IPv4 addresses, and so what? I can still watch Youtube videos, have calls over Zoom, buy things on Amazon. The Internet works due to the magic of NAT! Channeling Oprah, you get an address, you also get an address, and you too get the same address!&lt;/p&gt;
&lt;p&gt;Except this all works because of lots of money and effort.&lt;/p&gt;
&lt;p&gt;Effort because you can't just make a call to your friend over IPv4. You're both likely behind a &lt;a href="https://en.wikipedia.org/wiki/Network_address_translation?useskin=vector#One-to-many_NAT"&gt;one-to-many NAT&lt;/a&gt;, meaning you can't receive connections. So who will initiate one? &lt;a href="https://de.wikipedia.org/wiki/Session_Traversal_Utilities_for_NAT"&gt;STUN&lt;/a&gt; offers some painful, costly, partial, and annoying solutions involving having a dedicated address anyway.&lt;/p&gt;
&lt;p&gt;An IPv4 address costs money, too. A single IPv4 address costs &lt;a href="https://auctions.ipv4.global/prior-sales"&gt;26 USD in bulk&lt;/a&gt; as of 2024-11-05, and I'm paying for a 3EUR per month for a cheap VPS just for the sake of having one address for myself.&lt;/p&gt;
&lt;p&gt;Why bother? Because if you want a little place for yourself on the internet to serve stuff from, you need an address. Manage your own game server? Expose a quirky service? Have an actual, direct call? Seed torrents of your favorite classic art? Connect to your home network while away? I want to do all those things, so I bought myself an address.&lt;/p&gt;
&lt;p&gt;Of course, taking an address from the limited pool makes me part of the problem.&lt;/p&gt;
&lt;p&gt;But I want to connect to the plant watering system that I left at home! But I want to download stuff from the off-site backup that's at my friend's place, behind NAT! Yes, I could buy a server in a data center and install Wireguard there. I did it, it sucks. High pings, slow transfers. Help! Let me have direct connections.&lt;/p&gt;
&lt;h2&gt;IPv6&lt;/h2&gt;
&lt;p&gt;IPv6 comes to the rescue! The address pool is about 420^π bazillions addresses, so every address is really cheap. If you &lt;a href="https://old.reddit.com/r/ipv6/comments/16dahsc/how_do_i_as_a_private_person_apply_for_an_ipv6/"&gt;want to apply&lt;/a&gt; for a subnet, a random &lt;a href="https://voldeta.com/en/product/ipv6-pi-support-48/"&gt;500 EUR package&lt;/a&gt; gives you 2¹²⁸⁻⁴⁸=2⁸⁰ addresses, which is 2417851639229258137600 addresses per EUR yearly.&lt;/p&gt;
&lt;p&gt;Global IPv6 addresses are effectively free.&lt;/p&gt;
&lt;p&gt;If you have a decent Internet provider, they will assign you a /60 subnet and let all your LAN devices grab a /64. Now anyone can connect to you, so enjoy! And put up your firewall.&lt;/p&gt;
&lt;h2&gt;That /64 thing&lt;/h2&gt;
&lt;p&gt;What's that about a /64 subnet? A subnet for every device?&lt;/p&gt;
&lt;p&gt;Well, that's how IPv6's DHCP equivalent works. You can't normally get anything smaller than that through autoconfiguration. Which is not a problem if you have a decent Internet provider.&lt;/p&gt;
&lt;p&gt;But if you've got only a half-decent provider, they might only offer you a /64. That typically happens on mobile connections, but not only. (Also, this can happen if you like subdividing networks like Russian dolls. IPv4 you could just chain NATs, IPv6 has no NAT.)&lt;/p&gt;
&lt;p&gt;So what do you do?&lt;/p&gt;
&lt;h2&gt;OpenWRT&lt;/h2&gt;
&lt;p&gt;I don't believe I have to introduce &lt;a href="https://openwrt.org/"&gt;OpenWRT&lt;/a&gt; to any of my readers. Newcomers, this is &lt;strong&gt;the&lt;/strong&gt; Open Source operating system for routers.&lt;/p&gt;
&lt;p&gt;It can extend IPv6 connections to connected computers &lt;em&gt;even if there's only a /64 available&lt;/em&gt;! And it has this nifty Web interface called LuCi.&lt;/p&gt;
&lt;p&gt;While there are multiple guides for the command-line, there are no guides for configuring IPv6 forwarding for the Web interface. So here's mine.&lt;/p&gt;
&lt;h2&gt;IPv6 relay mode in Luci&lt;/h2&gt;
&lt;p&gt;First, set up an IPv4 WAN network (if you care). I'll call it &lt;em&gt;wwa&lt;/em&gt;n. Remember to set up a LAN interface if you don't have one. Ready? Then set up a WAN network for IPv6. I'l call it &lt;em&gt;relay6&lt;/em&gt;, set it to DHCP client and select "Alias Interface: @wwan" as the device.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Adding new interface &amp;quot;relay 6&amp;quot;" src="relay%20setup.png" /&gt;&lt;/p&gt;
&lt;p&gt;Once you have it, navigate to "DHCP server" and set one up.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Interfaces » relay6 → DHCP Server → button: Set up DHCP Server" src="dhcp%20setup.png" /&gt;&lt;/p&gt;
&lt;p&gt;Yes, I know, it's weird. We don't want to provide addresses on this interface. That's why the next step is checking the "Ignore interface" box.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Interfaces » relay6 → DHCP Server → General Setup → checkbox: Ignore interface" src="dhcp%20ignore.png" /&gt;&lt;/p&gt;
&lt;p&gt;Once that's done, go to DHCP IPv6 settings.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Interfaces » relay6 → DHCP Server → IPv6 Settings → unchecked &amp;quot;Designated master&amp;quot; checkbox and 3 drop-downs, each on &amp;quot;disabled&amp;quot;" src="empty%20settings.png" /&gt;&lt;/p&gt;
&lt;p&gt;Make this interface a designated master and change 3 dropdowns (RA-Service, DHCPv6-Service, NDP-Proxy) to "relay mode".&lt;/p&gt;
&lt;p&gt;&lt;img alt="Interfaces » relay6 → DHCP Server → IPv6 Settings → checked &amp;quot;Designated master&amp;quot; checkbox and 3 drop-downs set to &amp;quot;relay mode&amp;quot;" src="filled%20settings.png" /&gt;&lt;/p&gt;
&lt;p&gt;We're done with the WAN interface, but the LAN needs to be adjusted. Here, it's just a static address on a bridge device.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Interfaces » LAN → General Settings → Protocol: Static address" src="lan%20info.png" /&gt;&lt;/p&gt;
&lt;p&gt;Go to DHCP Server → IPv6 Settings and change all the dropdowns to "relay mode".&lt;/p&gt;
&lt;p&gt;&lt;img alt="Interfaces » LAN → DHCP Server → IPv6 Settings → unchecked &amp;quot;Designated master&amp;quot; checkbox and 3 drop-downs set to &amp;quot;relay mode&amp;quot;" src="lan%20settings.png" /&gt;&lt;/p&gt;
&lt;p&gt;Save all the changes and apply them. For me, the router immediately received an address.&lt;/p&gt;
&lt;p&gt;My laptop also got an address immediately, but I had to reconnect to get the default route populated (otherwise you can't connect to the Internet).&lt;/p&gt;
&lt;p&gt;Check a device connected to that router, it should get the address, too.&lt;/p&gt;
&lt;h3&gt;Default route&lt;/h3&gt;
&lt;p&gt;One snag, though.&lt;/p&gt;
&lt;p&gt;The default route was not set for my laptop. I had to modify the connection manually and add one. The result looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[me@foobar ~]$ ip -6 r
default via fe80::abcd:ef12:fecd:6573 dev wlp2s0 proto ra metric 600 pref high
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Bonus: ULA&lt;/h2&gt;
&lt;p&gt;ULA is something that doesn't exist in IPv4.&lt;/p&gt;
&lt;p&gt;ULA is the closest counterpart to an IPv4 local address. I use it to have stable addresses within my network. Even if the upstream changes their prefix (effectively your network address) and all previous addresses become invalid at 6:00 every morning, breaking all connections (thanks Telekom), and even if the upstream goes down at all, ULA will keep your network internally connected.&lt;/p&gt;
&lt;p&gt;I put the names of all hosts on my network in /etc/hosts, like a troglodyte on the early Internet before DNS.&lt;/p&gt;
&lt;p&gt;It sounds complicated if you come from IPv4. There, every computer knows its own address. 192.168.1.77. Great. That's it. What's my public address? No idea.&lt;/p&gt;
&lt;p&gt;In IPv6, every computer can easily have multiple addresses. A global one, some ULA addresses. It doesn't become a mess because those addresses are completely independent (link-local ones are a bit special, though). ULA networks are dropped on the edge of the public Internet, so they can't even be used for Internet access.&lt;/p&gt;
&lt;p&gt;OpenWRT supports using ULA in Network → Global network options.&lt;/p&gt;
&lt;p&gt;For me, it doesn't always get picked up, but it's there on some "Static address" interfaces. I don't really know what controls it, but I have one on the same interface as my LAN:&lt;/p&gt;
&lt;p&gt;&lt;img alt="&amp;quot;ula&amp;quot; named network on interface bridgr spanning several devices, with protocol &amp;quot;Static address&amp;quot; and an IPv6/64 ULA address without an IPv4 address" src="ula.png" /&gt;&lt;/p&gt;
&lt;h2&gt;The end&lt;/h2&gt;
&lt;p&gt;Now I have IPv6 in my local network! I can seed the world with torrents! I can host a quirky server for 10 minutes (remember to open firewall)! I can connect to the servers I manage at a friend's place!&lt;/p&gt;
&lt;p&gt;Oh wait, that server is behind its own firewall and I never enabled IPv6 on that one -_-. I guess I'm not done blogging about IPv6 yet.&lt;/p&gt;</description><author>dcz's posts</author><pubDate>Tue, 05 Nov 2024 16:00:00 GMT</pubDate><guid isPermaLink="true">https://dorotac.eu/posts/ipv6_homenet/</guid></item><item><title>Query rewriting - refine user queries</title><link>https://kaszkowiak.org/en/blog/query-rewriting/</link><description>What is query rewriting? Why is it useful whenever you build a chatbot? How to implement it? Read the article!</description><author>Blog on Maciej Kaszkowiak</author><pubDate>Tue, 05 Nov 2024 12:00:00 GMT</pubDate><guid isPermaLink="true">https://kaszkowiak.org/en/blog/query-rewriting/</guid></item><item><title>The new rollercoaster king</title><link>https://ilearnt.com/blog/hyperia/</link><description>&lt;p&gt;How do you start to design a new rollercoaster? How do you make it different and more memorable than those before it? And how do you end up doing it when you are only 27?&lt;/p&gt;</description><author>I Learnt</author><pubDate>Tue, 05 Nov 2024 11:18:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/hyperia/</guid></item><item><title>Quick and easy Python concurrency</title><link>https://brntn.me/blog/quick-and-easy-python-concurrency-with-multiprocessingpool/</link><description>&lt;p&gt;This post serves as a simple reminder to myself that &lt;a href="https://docs.python.org/dev/library/multiprocessing.html"&gt;&lt;code&gt;multiprocessing.Pool&lt;/code&gt;&lt;/a&gt; has &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;starmap&lt;/code&gt; functions that are a quick and easy way to take a loop and parallelise it in Python.&lt;/p&gt;
&lt;p&gt;Imagine some code like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;urls = [&amp;quot;https://example.org&amp;quot;,]

def take_screenshot(url):
    # ...

for url in urls:
    take_screenshot(url)
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;To make this run across four separate processes, you can do this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;pool = multiprocessing.Pool(4)
pool.map(take_screenshot, urls)
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Where does &lt;code&gt;starmap&lt;/code&gt; come into it? Well, it lets you invoke the function with multiple arguments by expanding the list you pass in.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;urls = [
    (&amp;quot;https://example.org&amp;quot;, &amp;quot;example.png&amp;quot;)
]

def take_screenshot(url, filename):
    # ...

pool = multiprocessing.Pool(4)
pool.starmap(take_screenshot, urls)
&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This is super handy for quick scripts, but as always there's trade-offs here.
Check out &lt;a href="http://masnun.rocks/2016/10/06/async-python-the-different-forms-of-concurrency/"&gt;this more detailed post&lt;/a&gt; to see other ways of managing concurrency in Python.&lt;/p&gt;</description><author>Brenton Cleeland</author><pubDate>Tue, 05 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://brntn.me/blog/quick-and-easy-python-concurrency-with-multiprocessingpool/</guid></item><item><title>4 Ways of Bumping Major Versions in Your Go Project</title><link>https://jerrynsh.com/4-ways-of-bumping-go-major-version/</link><description>&lt;p&gt;I&amp;apos;ve recently found myself in a rabbit hole of Go major version bumping to v2. What started as a simple task quickly turned into hours of sifting through conflicting information. Should I use a v2 directory? Create a new v2 branch? What about creating a new repository altogether?&lt;/p&gt;</description><author>Jerry Ng</author><pubDate>Tue, 05 Nov 2024 02:00:25 GMT</pubDate><guid isPermaLink="true">https://jerrynsh.com/4-ways-of-bumping-go-major-version/</guid></item><item><title>Notes on binary soup</title><link>https://www.marginalia.nu/log/a_112_slop_ideas/index.md/</link><description>&lt;p&gt;I recently put together a small library called &lt;a href="https://github.com/MarginaliaSearch/SlopData"&gt;Slop&lt;/a&gt;, for intermediate on-disk data representation for the search engine, replacing a few ad-hoc formats I had in place before.&lt;/p&gt;
&lt;p&gt;This post isn&amp;rsquo;t so much an attempt to convince anyone else to use this library, as it makes trade-offs catering to a fairly niche use case, but to explore some of its design ideas, as it all came together very nicely, in the hopes that other libraries can draw ideas from it.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Tue, 05 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_112_slop_ideas/index.md/</guid></item><item><title>Writing Multithreaded Node.js Applications: Is it worth it? at NodeConf EU</title><link>https://thomashunter.name/posts/2024-11-05-nodeconfeu-speaker</link><author>Thomas Hunter II</author><pubDate>Tue, 05 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://thomashunter.name/posts/2024-11-05-nodeconfeu-speaker</guid></item><item><title>Serverless Invoice Management System with AWS</title><link>https://prashamhtrivedi.in/invoicer/</link><description>Revolutionized invoice management with a serverless system using AWS Lambda, S3, DynamoDB, and Anthropic API. Reduced a hours-long task to 5 minutes.</description><author>Prasham H Trivedi</author><pubDate>Tue, 05 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/invoicer/</guid></item><item><title>What Goes Around Comes Around... And Around...</title><link>https://prashamhtrivedi.in/what_goes_around/</link><description>This is an interesting paper I read last month. The notes on this paper gave me an idea of having linkblogs&amp;hellip;
Original Paper link
Some quotes
Under 4: Parting comments
ORMs are a vital tool for rapid prototyping. But they often sacrifice the ability to push logic into the DBMS in exchange for interoperability with multiple DBMSs. Developers fall back to writing explicit database queries to override the poor auto-generated queries.</description><author>Prasham H Trivedi</author><pubDate>Tue, 05 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/what_goes_around/</guid></item><item><title>The next stage of getlocalcert.net</title><link>https://alexsci.com/blog/shutting-down-getlocalcert/</link><description>Keeping the project going</description><author>Built on Shards of Silicon: Robert Alexander's Tech Blog</author><pubDate>Tue, 05 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://alexsci.com/blog/shutting-down-getlocalcert/</guid></item><item><title>Effective unemployment and social media</title><link>http://notes.eatonphil.com/2024-11-05-effective-unemployment-and-social-media.html</link><description>&lt;p&gt;Being unemployed can be incredibly depressing. So much
rejection. Everything seems to be out of your control. Everything
except for one thing: what you produce.&lt;/p&gt;
&lt;p&gt;You might know that repeatedly posting on social media that you are
looking for work is ineffective. That it looks (or at least feels)
worse each time you say so. But there is at least one major caveat to
this.&lt;/p&gt;
&lt;p&gt;Every single time you create something and share it publicly is a
chance to also reiterate that you are looking for work. And people
actually appreciate and value this!&lt;/p&gt;
&lt;p&gt;Whether you write a blog post or build some project, you are seen as
working on yourself and contributing to the community. Positive
things! And it is no problem at all to learn with each new post you
write and each new project you publish that you are also looking for
work.&lt;/p&gt;
&lt;p&gt;Moreover, dynamics of the internet and social media basically require
that you be regularly producing something new. Either regularly
producing a new version of some existing project or regularly
producing new projects (or blog posts) entirely.&lt;/p&gt;
&lt;p&gt;What you did a week ago is old news on social media. What will you do
next week?&lt;/p&gt;
&lt;p&gt;This could itself feel depressing except for that it's probably
actually a fairly healthy thing for yourself anyway! It is a
motivation to keep your skills sharp as time goes on.&lt;/p&gt;
&lt;p&gt;So while you're unemployed and able to muster the motivation, write
about things that are interesting to you! Build projects that intrigue
you. Leave a little note on every post and project that you are
looking for work. And share every post and project on social media.&lt;/p&gt;
&lt;p&gt;You'll expose yourself to opportunities and referrals. And even if no
post or project "takes off" you will still be working on yourself and
contributing back knowledge to the community.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a short post on some ideas for effective unemployment and social media.&lt;a href="https://t.co/jmiJCOe2Nk"&gt;https://t.co/jmiJCOe2Nk&lt;/a&gt; &lt;a href="https://t.co/pK9AySNdHR"&gt;pic.twitter.com/pK9AySNdHR&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1853800075564109880?ref_src=twsrc%5Etfw"&gt;November 5, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 05 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-11-05-effective-unemployment-and-social-media.html</guid></item><item><title>SvelteKit Streaming: The Complete Guide</title><link>https://khromov.se/sveltekit-streaming-the-complete-guide/</link><description>&lt;p&gt;Streaming in SvelteKit is a powerful feature that allows you to load data progressively. In a nutshell, streaming allows your SvelteKit app to send an initial content response to the browser quickly, while fetching and sending additional data as it becomes available. This can make your app feel more responsive, especially when dealing with slow data sources. While streaming data loads you can easily show loading spinners. What is streaming? In a traditional SvelteKit application without streaming, the server waits until data from all load functions has finished loading before sending the complete response to the browser. This means users [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://khromov.se/sveltekit-streaming-the-complete-guide/"&gt;SvelteKit Streaming: The Complete Guide&lt;/a&gt; appeared first on &lt;a href="https://khromov.se"&gt;Stanislav Khromov&lt;/a&gt;.&lt;/p&gt;</description><author>Stanislav Khromov</author><pubDate>Mon, 04 Nov 2024 23:03:13 GMT</pubDate><guid isPermaLink="true">https://khromov.se/sveltekit-streaming-the-complete-guide/</guid></item><item><title>Pushed Authorization Requests (PAR) in ASP.NET Core 9</title><link>https://nestenius.se/net/pushed-authorization-requests-par-in-asp-net-core-9/</link><description>&lt;p&gt;ASP.NET Core 9 introduces support for Pushed Authorization Requests (PAR) in its OpenIdConnect authentication handler. But what exactly is PAR, and why does it matter? In this post, I&amp;#8217;ll explain what PAR is, how it works, how to use it with Duende IdentityServer, and when you should consider using it in your applications. What is [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/net/pushed-authorization-requests-par-in-asp-net-core-9/"&gt;Pushed Authorization Requests (PAR) in ASP.NET Core 9&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Mon, 04 Nov 2024 21:17:05 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/net/pushed-authorization-requests-par-in-asp-net-core-9/</guid></item><item><title>Getting started with LiteRT (Tensorflow Lite)</title><link>https://whackylabs.com/swift/tensorflow/ml/2024/11/04/getting-started-with-litert/</link><description>&lt;p&gt;So Google recently renamed TensorflowLite to LiteRT. And yes that was a genius move indeed. Because now for the first time in my life I actually want to try TFLite … yea, I mean LiteRT.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Setup" src="/assets/hello-litert/meme.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;In the real world you’d ideally think like a regular ML developer and start with discovering a dataset that then you’d use to train a model. 
And then as a next step you’d invent a problem that could be solved by your trained model.&lt;/p&gt;

&lt;p&gt;But for this experiment we are going to keep things simple and build the hello world of Machine Learning universe, the Dogs vs Cat exercise.&lt;/p&gt;

&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;
&lt;p&gt;So coincidentally enough I’ve found a trained model from &lt;a href="https://github.com/offfahad/cat-and-dog-detector-app-flutter"&gt;a flutter project&lt;/a&gt; that can tell if a given photo is of a dog or a cat.&lt;/p&gt;

&lt;p&gt;Nice! So then following the instructions at the &lt;a href="https://ai.google.dev/edge/litert/ios/quickstart"&gt;official getting started docs&lt;/a&gt; we need to add tensorflowlite as a dependency to our project.&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;use_frameworks!
# pod 'TensorFlowLiteSwift'
pod 'TensorFlowLiteSwift', '~&amp;gt; 0.0.1-nightly', :subspecs =&amp;gt; ['CoreML', 'Metal']
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In case you’re wondering why are we using the nightly build and not the stable release. It’s because as of today the latest stable release of &lt;code class="language-plaintext highlighter-rouge"&gt;TensorFlowLiteC&lt;/code&gt; &lt;a href="https://github.com/tensorflow/tensorflow/issues/47400"&gt;won’t work on iOS simulator&lt;/a&gt; but according to the &lt;a href="https://github.com/tensorflow/tensorflow/issues/47400#issuecomment-1126611267"&gt;last comment&lt;/a&gt; on the issue looks like &lt;code class="language-plaintext highlighter-rouge"&gt;TensorFlowLiteC&lt;/code&gt; is now also shipped as xcframework but only in the nightly releases.&lt;/p&gt;

&lt;p&gt;And then while we wait for the &lt;code class="language-plaintext highlighter-rouge"&gt;pod install&lt;/code&gt; to finish we can shamelessly rip sample images of various dogs and cats from the &lt;a href="https://www.udacity.com/enrollment/ud190"&gt;Introduction to TensorFlow Lite&lt;/a&gt; udacity course as our test set.&lt;/p&gt;

&lt;p&gt;And we are all set!&lt;/p&gt;

&lt;h3 id="building-the-app"&gt;Building the app&lt;/h3&gt;

&lt;p&gt;For the UI we just need a simple image view, a label and a button. The button obviously would reveal the answer and then randomly load the next image.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Setup" src="/assets/hello-litert/setup.png" /&gt;&lt;/p&gt;

&lt;p&gt;Now for the interesting bit. First we need a &lt;code class="language-plaintext highlighter-rouge"&gt;PetClassifier&lt;/code&gt; that takes in an image and returns a text.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;PetClassifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  
  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;labelForImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then in the UI layer we can use our &lt;code class="language-plaintext highlighter-rouge"&gt;PetClassifier&lt;/code&gt; to update the label when tapped on the ‘Evaluate’ button.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;PetClassifier&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;
  &lt;span class="kd"&gt;@IBOutlet&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;answerLabel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UILabel&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;classifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;PetClassifier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"dogvscat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"Cat"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Dog"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;@IBAction&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleTap&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;answerLabel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;classifier&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;labelForImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selectedImage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"Potato"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, loading the model is pretty easy. We just need to instantiate &lt;code class="language-plaintext highlighter-rouge"&gt;Interpreter&lt;/code&gt; with the path to our tflite model.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;PetClassifier&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;interpreter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Interpreter&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    
  &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;?(&lt;/span&gt;&lt;span class="nv"&gt;named&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;modelPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Bundle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forResource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;named&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;ofType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"tflite"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    
    &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Interpreter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threadCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;threadCount&lt;/span&gt;
      &lt;span class="n"&gt;interpreter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Interpreter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;modelPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;modelPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id="invoking-the-model"&gt;Invoking the model&lt;/h3&gt;

&lt;p&gt;To get the answer from the model is a 4 step process:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Prepare input&lt;/li&gt;
  &lt;li&gt;Send input data&lt;/li&gt;
  &lt;li&gt;Read output data&lt;/li&gt;
  &lt;li&gt;Parse output&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="cm"&gt;/*
 * 1. Prepare input
 */&lt;/span&gt;
&lt;span class="c1"&gt;// user provided image&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIImage&lt;/span&gt; 
&lt;span class="c1"&gt;// image size used for training model&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;inputWidth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;inputHeight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;224&lt;/span&gt;

&lt;span class="c1"&gt;// convert image to pixel buffer for further manipulation&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pixelBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ImageUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pixelBufferCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// crop image to size used for training model&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;scaledPixelBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ImageUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pixelBufferCreateWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nv"&gt;resizedTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inputWidth&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inputHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Remove the alpha component from the image buffer to get the RGB data.&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;rgbData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;ImageUtils&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pixelBufferCreateRGBData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;pixelBuffer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;scaledPixelBuffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;byteCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inputWidth&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inputHeight&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="cm"&gt;/*
 * 2. Send input data 
 */&lt;/span&gt;
&lt;span class="n"&gt;interpreter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allocateTensors&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;      
&lt;span class="n"&gt;interpreter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rgbData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;toInputAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;interpreter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="cm"&gt;/*
 * 3. Read output data
 */&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;outputTensor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;interpreter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;output&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;outputTensor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;withUnsafeBytes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bindMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Float&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cm"&gt;/*
 * 4. Parse output
 */&lt;/span&gt;      
&lt;span class="c1"&gt;// Create a zipped array of tuples [(labelIndex: Int, confidence: Float)].&lt;/span&gt;
&lt;span class="c1"&gt;// Sort the zipped results by confidence value&lt;/span&gt;
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;inferences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;indices&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sorted&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nv"&gt;confidence&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      
&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;bestInference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;inferences&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And there you have it. That is how offload your brain to your computer.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Setup" src="/assets/hello-litert/final.png" /&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code class="language-plaintext highlighter-rouge"&gt;ImageUtils&lt;/code&gt; from this experiment are available &lt;a href="https://gist.github.com/chunkyguy/1e5244c4dcee8436d700f99380629989"&gt;here&lt;/a&gt;. But there are probably better libraries for these operations. For example, the &lt;a href="https://github.com/hollance/CoreMLHelpers"&gt;CoreMLHelpers&lt;/a&gt;&lt;/p&gt;

&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://developers.googleblog.com/en/tensorflow-lite-is-now-litert/"&gt;TensorFlow Lite is now LiteRT&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.kaggle.com/models?framework=tfLite"&gt;Kaggle&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://huggingface.co/models?library=tflite"&gt;HuggingFace&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ai.google.dev/edge/mediapipe/solutions/genai/llm_inference/ios"&gt;LLM Inference guide for iOS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Mon, 04 Nov 2024 21:00:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/swift/tensorflow/ml/2024/11/04/getting-started-with-litert/</guid></item><item><title>Practical Deep Learning, Lesson 4, Language Model Blog Post Imitator</title><link>https://www.danielcorin.com/til/fastai/lesson4-blog-post-imitator/</link><description>Practical Deep Learning, Lesson 4, Language Model Blog Post Imitator</description><author>Thought Eddies</author><pubDate>Mon, 04 Nov 2024 19:57:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/fastai/lesson4-blog-post-imitator/</guid></item><item><title>Hurdling hierarchy</title><link>https://ilearnt.com/blog/hurdlinghierarchy/</link><description>&lt;p&gt;Often people are afraid to reach out to those above them in the hierarchy or in a different team, even if it is the best solution to their situation.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Mon, 04 Nov 2024 16:59:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/hurdlinghierarchy/</guid></item><item><title>Heads or Tails</title><link>andersource.github.io/2026/03/29/andersource.github.io/2024/11/04/heads-or-tails.html</link><description>Conceptual introduction to classical machine learning in JS</description><author>andersource</author><pubDate>Mon, 04 Nov 2024 16:30:00 GMT</pubDate><guid isPermaLink="true">andersource.github.io/2026/03/29/andersource.github.io/2024/11/04/heads-or-tails.html</guid></item><item><title>Recursive Query in SQL: What It Is, and How to Write One</title><link>https://dylanpaulus.com/posts/2024/recursive-query-in-sql-what-it-is-and-how-to-write-one/</link><description>&lt;p&gt;As developers, querying in PostgreSQL for hierarchical data is difficult. SQL is a declarative programming language, but our brains are trained to think imperatively. Recursive queries using Common Table Expressions (CTE) simplify writing iterative queries and are essential in traversing hierarchical data, like tree or graph structures.&lt;/p&gt;
&lt;p&gt;Throughout this article, we'll explore SQL recursive queries with examples, look at optimizing recursive queries, and discuss advanced techniques.&lt;/p&gt;</description><author>Dylan Paulus' Blog</author><pubDate>Mon, 04 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://dylanpaulus.com/posts/2024/recursive-query-in-sql-what-it-is-and-how-to-write-one/</guid></item><item><title>Why I love Rust for tokenising and parsing</title><link>https://xnacly.me/posts/2024/rust-pldev/</link><description>Macros, iterators, patterns, error handling and match make Rust almost perfect</description><author>xnacly - blog</author><pubDate>Mon, 04 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2024/rust-pldev/</guid></item><item><title>Parsing arguments in Rust with no dependencies</title><link>https://ntietz.com/blog/parsing-arguments-rust-no-deps/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;When pairing with my friend Emily, we had a choice of what to implement in her project:
start a new feature, or add a command line argument parser?
We opted for the latter, because it had to happen eventually and it was more well bounded.
It ended up having a lot of depth!&lt;/p&gt;
&lt;p&gt;We wrote it from scratch to learn more, rather than pulling in a library&lt;sup class="footnote-reference" id="fr-cplusplus-1"&gt;&lt;a href="https://ntietz.com/blog/parsing-arguments-rust-no-deps/#fn-cplusplus"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
What we found was a nice level of depth in a well-bounded project.
I came away wanting to repeat the exercise in Rust.&lt;/p&gt;
&lt;h1 id="opportunity-arrives-on-a-yak"&gt;Opportunity arrives on a yak&lt;/h1&gt;
&lt;p&gt;As with many of my best procrastination projects, the chance to do this again in Rust came in the form of a yak shave.
I've started writing a little poetry, and to get practice, I wanted to write a small utility to generate prompts&lt;sup class="footnote-reference" id="fr-hahano-1"&gt;&lt;a href="https://ntietz.com/blog/parsing-arguments-rust-no-deps/#fn-hahano"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.
Naturally, this has to be &lt;em&gt;configurable&lt;/em&gt; because my little utility will &lt;em&gt;sweep the world&lt;/em&gt; (spoiler alert: no it won't).
That means we need command-line arguments!&lt;/p&gt;
&lt;p&gt;I could pull in &lt;a href="https://docs.rs/clap/latest/clap/index.html"&gt;clap&lt;/a&gt;, which I think is the best fully-featured choice available today.
That would add 23 dependencies to my little project, if you count transitive dependencies.
This can go up higher if you turn on a few features: &lt;code&gt;derive&lt;/code&gt;, &lt;code&gt;env&lt;/code&gt;, &lt;code&gt;unicode&lt;/code&gt;, and &lt;code&gt;wrap_help&lt;/code&gt; bring you up to 38 dependencies!&lt;/p&gt;
&lt;p&gt;Instead, I thought it would be fun to implement it myself again, and do it in the same mode as our C++ adventure: parse command-line arguments with &lt;em&gt;no&lt;/em&gt; external dependencies!
This will have the side benefit of keeping my project's dependencies much lighter, which should (I think) keep compile times lower and make the entire system easier to understand front-to-back.
If I do this, I may as well publish it and make it usable for everyone!&lt;/p&gt;
&lt;h1 id="the-basic-design"&gt;The basic design&lt;/h1&gt;
&lt;p&gt;The design of this parser is pretty straightforward.
We have two types of arguments: positional or named.&lt;/p&gt;
&lt;p&gt;We'll handle positional arguments as little more than a list of the leftover strings that we don't need for named options.
These don't get any special handling, and there's not a lot of parsing to do (unless you do fancy things like path and variable expansion).&lt;/p&gt;
&lt;p&gt;Named options (and their respective arguments) are where we have more work to do.
We want this to feel familiar, like most unix/linux/etc. command-line interfaces we've used.
So we'll want the usual short and long options.
For example, specifying a port might be able to use the short option &lt;code&gt;-p&lt;/code&gt; or the long option &lt;code&gt;--port-number&lt;/code&gt;.
And we'll also want to be able to specify some help text, and whether or not the argument is required or optional.&lt;/p&gt;
&lt;p&gt;Where things might be a little bit unfamiliar is with the action.
We have to have a way of specifying whether something is a flag, a single value, or a list of values.
Libraries like clap have more comprehensive options for actions, but this should be sufficient for us here.&lt;/p&gt;
&lt;p&gt;And then to do our parsing, we can think of it like a state machine.
You iterate through the provided arguments until you find one that could be the start of option (starts with a hyphen).
Then you find the matching option and switch into a state based on what you expect to come next.
After handling its argument (if there is one), you go back to the default state and look for the next option.&lt;/p&gt;
&lt;p&gt;So now we just have to build that.&lt;/p&gt;
&lt;h1 id="implementing-it"&gt;Implementing it&lt;/h1&gt;
&lt;p&gt;The data structures we use here match what was described previously in perhaps the most straightforward way.
We have a struct to represent each of our options (here using &lt;code&gt;Opt&lt;/code&gt; since &lt;code&gt;Option&lt;/code&gt; is a highly useful existing struct in the standard library; we can never escape names being hard and ambiguous).
For ergonomics, we'll also define methods to set each of these easily and return self; I have one example here, but the rest are the same idea.
We also use a &lt;code&gt;name&lt;/code&gt; method as the constructor, since all named arguments must have names.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;derive&lt;/span&gt;&lt;span&gt;(Clone, Debug, PartialEq)]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub struct &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;Opt &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;name&lt;/span&gt;&lt;span&gt;: String,
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;short&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Option&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;char&lt;/span&gt;&lt;span&gt;&amp;gt;,
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;long&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Option&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;String&lt;/span&gt;&lt;span&gt;&amp;gt;,
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;help&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Option&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;String&lt;/span&gt;&lt;span&gt;&amp;gt;,
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;default&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Option&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;String&lt;/span&gt;&lt;span&gt;&amp;gt;,
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;action&lt;/span&gt;&lt;span&gt;: Action,
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;required&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;bool&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;impl &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;Opt &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;short&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;short&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;char&lt;/span&gt;&lt;span&gt;) -&amp;gt; Opt {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;.short &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(short);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-style: italic; color: #928374;"&gt;// same idea for the other fields
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we have a couple of enums for some values we need&lt;sup class="footnote-reference" id="fr-values-1"&gt;&lt;a href="https://ntietz.com/blog/parsing-arguments-rust-no-deps/#fn-values"&gt;[3]&lt;/a&gt;&lt;/sup&gt;.
The first enum is for our actions.
These tell the parser what to do when it encounters an option while parsing.
&lt;code&gt;Set&lt;/code&gt; and &lt;code&gt;Append&lt;/code&gt; will slurp up the next argument as the value and store it or insert it.
&lt;code&gt;SetTrue&lt;/code&gt; and &lt;code&gt;SetFalse&lt;/code&gt; will each set the boolean for their respective values.
Note that each of the set flags implies that the &lt;em&gt;opposite&lt;/em&gt; is the default value (and we'll implement it that way).&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;derive&lt;/span&gt;&lt;span&gt;(Clone, Debug, PartialEq)]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub enum &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;Action &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    Set,
&lt;/span&gt;&lt;span&gt;    Append,
&lt;/span&gt;&lt;span&gt;    SetTrue,
&lt;/span&gt;&lt;span&gt;    SetFalse,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we have a what we end up storing for the values we've parsed.
I opted to keep this simple and not introduce any extra types here.
I think this is a reasonable tradeoff, since it keeps the code simple and it remains flexible, but the cost is that this means checking if an incorrect value is passed in (say, "fiddlesticks" for something that expects an integer) is pushed off as the user's responsibility, making the library harder to use.&lt;/p&gt;
&lt;p&gt;Anyway, we have three choices: a single value, a list of values, or a true/false value.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;derive&lt;/span&gt;&lt;span&gt;(Clone, Debug, PartialEq)]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub enum &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;Value &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    Single(&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;String&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;    Multi(&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;String&lt;/span&gt;&lt;span&gt;&amp;gt;),
&lt;/span&gt;&lt;span&gt;    Flag(&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;bool&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we have one struct to put it all together and define our parsing method on.
This one is truly spartan.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;derive&lt;/span&gt;&lt;span&gt;(Debug, PartialEq)]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub struct &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;Opts &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;opts&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Vec&lt;/span&gt;&lt;span&gt;&amp;lt;Opt&amp;gt;,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we have our resulting matches, after we've parsed things.
Here we'll store one thing we haven't talked about yet, the name of the executable (the first argument passed in), and then the positional and named arguments.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;derive&lt;/span&gt;&lt;span&gt;(Debug, PartialEq)]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;pub struct &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;Matches &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;exec_name&lt;/span&gt;&lt;span&gt;: String,
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;positional&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;String&lt;/span&gt;&lt;span&gt;&amp;gt;,
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;named&lt;/span&gt;&lt;span&gt;: HashMap&amp;lt;&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;String&lt;/span&gt;&lt;span&gt;, Value&amp;gt;,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now that we have the data structures, we can implement the parsing itself.&lt;/p&gt;
&lt;p&gt;We'll start by making a &lt;code&gt;parse&lt;/code&gt; method on &lt;code&gt;Opts&lt;/code&gt;.
This will take in our arguments (from something like &lt;code&gt;std::env::args&lt;/code&gt;) and return our matches, or an error.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;pub fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;parse&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;args&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Vec&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;String&lt;/span&gt;&lt;span&gt;&amp;gt;) -&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Result&lt;/span&gt;&lt;span&gt;&amp;lt;Matches, ParseError&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;todo!&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can simplify our lives a bit by setting all the default values at the beginning, letting them get overwritten later if another value is provided.
This is not the most efficient approach!
But it is going to be a rounding error for most programs, and we could improve the implementation later to run it &lt;em&gt;last&lt;/em&gt; and only fill the arguments which aren't provided (or to fill them when we &lt;em&gt;request&lt;/em&gt; them).
Let's just do this for now, though.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;populate_defaults&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;named&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut &lt;/span&gt;&lt;span&gt;HashMap&amp;lt;&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;String&lt;/span&gt;&lt;span&gt;, Value&amp;gt;) {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;for&lt;/span&gt;&lt;span&gt; opt &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;in &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;.opts.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;iter&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if let &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(default) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &amp;amp;&lt;/span&gt;&lt;span&gt;opt.default {
&lt;/span&gt;&lt;span&gt;            named.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;insert&lt;/span&gt;&lt;span&gt;(opt.name.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;clone&lt;/span&gt;&lt;span&gt;(), Value::Single(default.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;to_owned&lt;/span&gt;&lt;span&gt;()));
&lt;/span&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;else &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;match&lt;/span&gt;&lt;span&gt; opt.action {
&lt;/span&gt;&lt;span&gt;                Action::Append &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                    named.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;insert&lt;/span&gt;&lt;span&gt;(opt.name.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;clone&lt;/span&gt;&lt;span&gt;(), Value::Multi(&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;vec!&lt;/span&gt;&lt;span&gt;[]));
&lt;/span&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;span&gt;                Action::SetTrue &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                    named.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;insert&lt;/span&gt;&lt;span&gt;(opt.name.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;clone&lt;/span&gt;&lt;span&gt;(), Value::Flag(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;false&lt;/span&gt;&lt;span&gt;));
&lt;/span&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;span&gt;                Action::SetFalse &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                    named.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;insert&lt;/span&gt;&lt;span&gt;(opt.name.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;clone&lt;/span&gt;&lt;span&gt;(), Value::Flag(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;false&lt;/span&gt;&lt;span&gt;));
&lt;/span&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;span&gt;                &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;_ =&amp;gt; &lt;/span&gt;&lt;span&gt;{}
&lt;/span&gt;&lt;span&gt;            }
&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then we can make a helper function to find the named option which matches a given option, if any, and returns an error if an unexpected option is provided.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;find_opt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;arg&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;str&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Result&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;Opt, ParseError&amp;gt; {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; opt &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if&lt;/span&gt;&lt;span&gt; arg.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;starts_with&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;--&amp;quot;&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; long &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span&gt; arg.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;strip_prefix&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;--&amp;quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;unwrap&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;.opts.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;iter&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;find&lt;/span&gt;&lt;span&gt;(|&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;o&lt;/span&gt;&lt;span&gt;| o.long.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;as_deref&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;== &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(long))
&lt;/span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;else if&lt;/span&gt;&lt;span&gt; arg.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;starts_with&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if&lt;/span&gt;&lt;span&gt; arg.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;chars&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;count&lt;/span&gt;&lt;span&gt;() &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;!= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;2 &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Err&lt;/span&gt;&lt;span&gt;(ParseError::MalformedOption(arg.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;to_string&lt;/span&gt;&lt;span&gt;()));
&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; short &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span&gt; arg.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;chars&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;nth&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;1&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;.opts.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;iter&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;find&lt;/span&gt;&lt;span&gt;(|&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;o&lt;/span&gt;&lt;span&gt;| o.short &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;==&lt;/span&gt;&lt;span&gt; short)
&lt;/span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;else &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Err&lt;/span&gt;&lt;span&gt;(ParseError::UnexpectedOption(arg.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;to_string&lt;/span&gt;&lt;span&gt;()));
&lt;/span&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if let &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(opt) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span&gt; opt {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;(opt)
&lt;/span&gt;&lt;span&gt;    } &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;else &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Err&lt;/span&gt;&lt;span&gt;(ParseError::UnexpectedOption(arg.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;to_string&lt;/span&gt;&lt;span&gt;()))
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And now we can come back and define our parse function.
It starts out with converting the args into an iterator (so we don't have to clone each string, we take ownership of them all).
If there isn't a first one, we know the whole thing has gone wrong, so we abort there.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="font-style: italic; color: #928374;"&gt;// pub fn parse(&amp;amp;self, args: Vec&amp;lt;String&amp;gt;) -&amp;gt; Result&amp;lt;Matches, ParseError&amp;gt; {
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let mut&lt;/span&gt;&lt;span&gt; args_iter &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span&gt; args.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;into_iter&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; exec_name &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;match&lt;/span&gt;&lt;span&gt; args_iter.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;next&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(s) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt;&lt;/span&gt;&lt;span&gt; s,
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;None &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Err&lt;/span&gt;&lt;span&gt;(ParseError::MissingProgramName),
&lt;/span&gt;&lt;span&gt;    };
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we setup our storage and populate the defaults with our helper function.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="font-style: italic; color: #928374;"&gt;// pub fn parse(&amp;amp;self, args: Vec&amp;lt;String&amp;gt;) -&amp;gt; Result&amp;lt;Matches, ParseError&amp;gt; {
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let mut&lt;/span&gt;&lt;span&gt; positional &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;vec!&lt;/span&gt;&lt;span&gt;[];
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let mut&lt;/span&gt;&lt;span&gt; named &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;HashMap::new();
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;populate_defaults&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;mut&lt;/span&gt;&lt;span&gt; named);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then we do our main loop.
Inside the main parse loop, we check if any start with a dash (&lt;code&gt;-&lt;/code&gt;): if so, we handle it as a named named option, otherwise we handle it as positional (just push it into a vec).
For each named option we have to find the right Opt, then we do the action: set the value, append it into a list, or set a flag.
There's a little error handling which makes it all somewhat longer, but overall the logic is simple.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="font-style: italic; color: #928374;"&gt;// pub fn parse(&amp;amp;self, args: Vec&amp;lt;String&amp;gt;) -&amp;gt; Result&amp;lt;Matches, ParseError&amp;gt; {
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;while let &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(arg) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span&gt; args_iter.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;next&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if&lt;/span&gt;&lt;span&gt; arg.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;starts_with&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;-&amp;quot;&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; opt &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;self&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;find_opt&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;arg)&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;?&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;            &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;match&lt;/span&gt;&lt;span&gt; opt.action {
&lt;/span&gt;&lt;span&gt;                Action::Set &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if let &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(value) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span&gt; args_iter.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;next&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;                        named.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;insert&lt;/span&gt;&lt;span&gt;(opt.name.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;clone&lt;/span&gt;&lt;span&gt;(), Value::Single(value));
&lt;/span&gt;&lt;span&gt;                    } &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;else &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Err&lt;/span&gt;&lt;span&gt;(ParseError::MissingValue(opt.name.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;clone&lt;/span&gt;&lt;span&gt;()));
&lt;/span&gt;&lt;span&gt;                    }
&lt;/span&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;span&gt;                Action::Append &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;match &lt;/span&gt;&lt;span&gt;(args_iter.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;next&lt;/span&gt;&lt;span&gt;(), named.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;get_mut&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;opt.name)) {
&lt;/span&gt;&lt;span&gt;                        (&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;None&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;_&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Err&lt;/span&gt;&lt;span&gt;(ParseError::MissingValue(opt.name.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;clone&lt;/span&gt;&lt;span&gt;())),
&lt;/span&gt;&lt;span&gt;                        (&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(val), &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(Value::Multi(vals))) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                            vals.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;push&lt;/span&gt;&lt;span&gt;(val);
&lt;/span&gt;&lt;span&gt;                        }
&lt;/span&gt;&lt;span&gt;                        (&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Some&lt;/span&gt;&lt;span&gt;(val), &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;None&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                            named.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;insert&lt;/span&gt;&lt;span&gt;(opt.name.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;clone&lt;/span&gt;&lt;span&gt;(), Value::Multi(&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;vec!&lt;/span&gt;&lt;span&gt;[val]));
&lt;/span&gt;&lt;span&gt;                        }
&lt;/span&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;_ =&amp;gt; &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Err&lt;/span&gt;&lt;span&gt;(ParseError::BadInternalState), &lt;/span&gt;&lt;span style="font-style: italic; color: #928374;"&gt;// unexpected case
&lt;/span&gt;&lt;span&gt;                    };
&lt;/span&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;span&gt;                Action::SetTrue &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                    named.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;insert&lt;/span&gt;&lt;span&gt;(opt.name.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;clone&lt;/span&gt;&lt;span&gt;(), Value::Flag(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;true&lt;/span&gt;&lt;span&gt;));
&lt;/span&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;span&gt;                Action::SetFalse &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&amp;gt; &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;                    named.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;insert&lt;/span&gt;&lt;span&gt;(opt.name.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;clone&lt;/span&gt;&lt;span&gt;(), Value::Flag(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;false&lt;/span&gt;&lt;span&gt;));
&lt;/span&gt;&lt;span&gt;                }
&lt;/span&gt;&lt;span&gt;            };
&lt;/span&gt;&lt;span&gt;        } &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;else &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;            positional.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;push&lt;/span&gt;&lt;span&gt;(arg);
&lt;/span&gt;&lt;span&gt;        }
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Ok&lt;/span&gt;&lt;span&gt;(Matches::new(exec_name, positional, named))
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And there we have it, a command-line options parser with &lt;em&gt;no&lt;/em&gt; external dependencies!&lt;/p&gt;
&lt;p&gt;There are a few things left that aren't done:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No help or versions print&lt;/li&gt;
&lt;li&gt;There's not a lot of help for validation&lt;/li&gt;
&lt;li&gt;Things aren't exported in the root module, so using it is clunky&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'll fix these as I use it, but if anyone actually uses this and wants to contribute or maintain it, let me know.
For now, the code is &lt;a href="https://git.sr.ht/~ntietz/ltl-args"&gt;in a git repo&lt;/a&gt; and licensed under MIT and Apache licenses.
This is a deviation from my usual use of the &lt;a href="https://gal.gay/"&gt;Gay Agenda License&lt;/a&gt; because this is a project that I suspect could have some small real utility.
Anyway, be gay do crime.&lt;/p&gt;
&lt;h1 id="this-no-dependencies-thing"&gt;This "no-dependencies" thing&lt;/h1&gt;
&lt;p&gt;Okay, but why am I yammering on about no dependencies so much?
Because there are some fundamentally nice things about &lt;em&gt;not&lt;/em&gt; adding more dependencies to your project, and sometimes we should reinvent things.&lt;/p&gt;
&lt;p&gt;One hypothesis is that with no dependencies, the compile times will be faster.
This isn't &lt;em&gt;broadly&lt;/em&gt; true (splitting a crate into subcrates can improve compile times, and this is a common practice), but I think &lt;em&gt;here&lt;/em&gt; it will improve compile times because this keeps it smaller and leaner.&lt;/p&gt;
&lt;p&gt;More concretely, by having no external dependencies you reduce your bug surface area.
Sure, you &lt;em&gt;own&lt;/em&gt; all the bugs now—but you won't get leftpad-ed, and you won't get dependabot alerts for third-removed transitive dependencies that now you've gotta patch.&lt;/p&gt;
&lt;p&gt;On the other hand, you miss out on nice things.
Here in particular, I'll be missing partial unicode support!
You could put whatever you want in the strings, but once I implement help text, I'll probably have some form of text wrapping.
And if you do that, you have to know how &lt;em&gt;wide&lt;/em&gt; characters are when displayed.
Not all of them are the same as one monospace Latin alphabet character (such as emoji and some languages), and it depends on your font as well I think?
We can do a best-effort job here of splitting based on some assumptions (and allow manual line splitting).
But the best idea would probably be to optionally add a dependency as a Cargo feature so that you only get the dependency if you need it.&lt;/p&gt;
&lt;p&gt;I think more things should be built from scratch and, ideally, without dependencies.
You get to know the problem space better, and most things don't &lt;em&gt;need&lt;/em&gt; the big sophisticated solution—but you pay for the &lt;em&gt;whole&lt;/em&gt; dependency you pull in.
It's also nice because if you have no dependencies, that means folks can depend on &lt;em&gt;you&lt;/em&gt; without adding any &lt;em&gt;transitive&lt;/em&gt; dependencies, and this is really a big deal.
I'd love to have a set of dependencies to use that all have either no dependencies, or themselves have only 0-dep dependencies, so your number of transitive dependencies is capped.
Then you could add in a lot of "fit for purpose" simple 0-dep things, and pick up the complex ones for where you &lt;em&gt;really&lt;/em&gt; need it while still keeping your &lt;code&gt;Cargo.lock&lt;/code&gt; slimmer than today.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-cplusplus"&gt;
&lt;p&gt;Besides, her project is in C++.
I don't know it well anymore, and wasn't eager to learn its package manger ecosystem.
For all the justification of reasons to build it ourselves, "it's more fun" and "we learn more" were the big two. &lt;a href="https://ntietz.com/blog/parsing-arguments-rust-no-deps/#fr-cplusplus-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-hahano"&gt;
&lt;p&gt;This is probably &lt;em&gt;not&lt;/em&gt; something I'll finish due to life and health.
But it &lt;em&gt;did&lt;/em&gt; spawn this nice yak shave. &lt;a href="https://ntietz.com/blog/parsing-arguments-rust-no-deps/#fr-hahano-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-values"&gt;
&lt;p&gt;I could've also made an enum for whether or not it's required, but this feels like one of the rare cases where a boolean is already clear and there's no risk that we'll extend it to a third choice. &lt;a href="https://ntietz.com/blog/parsing-arguments-rust-no-deps/#fr-values-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 04 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/parsing-arguments-rust-no-deps/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Is email confidential in transit yet?</title><link>https://alexsci.com/blog/is-email-confidential-in-transit-yet/</link><description>Measuring vulnerable SMTP configurations and defenses</description><author>Built on Shards of Silicon: Robert Alexander's Tech Blog</author><pubDate>Mon, 04 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://alexsci.com/blog/is-email-confidential-in-transit-yet/</guid></item><item><title>GraphQL collection lookups</title><link>https://sophiabits.com/blog/graphql-collection-lookups</link><description>&lt;p&gt;One of the powerful design patterns that can be employed within a GraphQL schema is the concept of &lt;strong&gt;collection lookups&lt;/strong&gt;. This pattern allows API consumers to retrieve specific elements from a collection using singular fields, enhancing both usability and performance.&lt;/p&gt;&lt;p&gt;To demonstrate this pattern, let’s imagine we’re building a blogging platform (perhaps for sophiabits.com!). The core data type we’ll focus on is a &lt;code&gt;Post&lt;/code&gt; which—among other things—shall contain a list of arbitrary key-value pairs and a list of images. Attributes are used by bloggers to store little tidbits of information that could be consumed by a plugin system.&lt;/p&gt;&lt;p&gt;Below is a potential schema. Note the paired &lt;code&gt;attribute&lt;/code&gt;/&lt;code&gt;attributes&lt;/code&gt; and &lt;code&gt;image&lt;/code&gt;/&lt;code&gt;images&lt;/code&gt; fields:&lt;/p&gt;&lt;p&gt;&lt;em&gt;Read more on &lt;a href="https://sophiabits.com/blog/graphql-collection-lookups"&gt;sophiabits.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description><author>Sophia Willows' Blog</author><pubDate>Mon, 04 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://sophiabits.com/blog/graphql-collection-lookups</guid></item><item><title>You probably shouldn't hire a Developer Advocate yet</title><link>https://lengrand.fr/you-probably-shouldnt-hire-a-developer-advocate-yet/</link><description>Many companies with job ads out for Developer Advocates don't know that's not what they should be hiring for (yet). More often than not, what they need first is some internal mindset change. And even then, a Developer Experience will go a long way to achieve the first stages of a healthy community.</description><author>Julien's DevRel corner</author><pubDate>Sun, 03 Nov 2024 19:46:31 GMT</pubDate><guid isPermaLink="true">https://lengrand.fr/you-probably-shouldnt-hire-a-developer-advocate-yet/</guid></item><item><title>The ultimate in debugging</title><link>https://ilearnt.com/blog/ultimatedebugging/</link><description>&lt;p&gt;Engineers are currently debugging why the Voyager 1 spacecraft, which is 15 billions miles away, turned off its main radio and switched to a backup radio that hasn&amp;rsquo;t been used in over forty years!&lt;/p&gt;</description><author>I Learnt</author><pubDate>Sun, 03 Nov 2024 19:29:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/ultimatedebugging/</guid></item><item><title>An unknown pioneer of computing</title><link>https://ilearnt.com/blog/edroberts/</link><description>&lt;p&gt;Ed Roberts is a forgotten person in the history of home computing but played a huge role in getting us to where we are today.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Sun, 03 Nov 2024 18:32:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/edroberts/</guid></item><item><title>Eric (Netflix Limited Series): Inner conflicts in New York's Landscape</title><link>https://olshansky.info/tv/eric_season_1/</link><description>Olshansky's review of Eric: Limited Series</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 03 Nov 2024 14:16:57 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/eric_season_1/</guid></item><item><title>Helping to solve male loneliness</title><link>https://ilearnt.com/blog/theshed/</link><description>&lt;p&gt;It is very easy to go through life and not realise the effects of loneliness.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Sun, 03 Nov 2024 11:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/theshed/</guid></item><item><title>1% Better Every Day</title><link>https://www.sunilshenoy.com/2024/11/03/better-every-day.html</link><description>&lt;p&gt;I remember seeing this message on one of my gym friends' t shirt a few months ago. It&amp;rsquo;s not the first time I saw this message—I had seen it countless times before—but this was the first time it stayed with me.&lt;/p&gt;
&lt;p&gt;I was listening to &amp;ldquo;The One Thing&amp;rdquo; podcast while driving around Sydney last week, and in one of the episodes, they spoke about how, when they got started with their project, they decided to spend one hour working on the thing they were passionate about. An hour a day dedicated to things they wanted to see in the world.&lt;/p&gt;
&lt;p&gt;&amp;ldquo;I could dedicate an hour a day to work on something I want to see in the world,&amp;rdquo; I told myself. I have had a long list of projects that I&amp;rsquo;ve been wanting to work on, but between a full-time job, gym, social life, and reading, I never took time to work on the projects I wanted to see in the world. The podcast was a great reminder to start. Getting older was a great reminder to start.&lt;/p&gt;
&lt;p&gt;The last two weeks have been exciting. I wake up with plans for that one hour. There is something to look forward to every day. There is no pressure to plan that one hour, but I have managed to work on one of the projects I&amp;rsquo;ve had in mind for a few years now. The goal is to launch it before my birthday. Having a deadline to ship has been great. Constraints are good. I have been pushing code out every day, but I am not yet ready to share the URL with everyone.&lt;/p&gt;
&lt;p&gt;Is there something you have been wanting to get started with?&lt;/p&gt;</description><author>Sunil Shenoy</author><pubDate>Sun, 03 Nov 2024 00:16:31 GMT</pubDate><guid isPermaLink="true">https://www.sunilshenoy.com/2024/11/03/better-every-day.html</guid></item><item><title>How I fixed my Nothing Ear muffled/underwater audio on Android (hint: it's ChatGPT)</title><link>https://liza.io/how-i-fixed-my-nothing-ear-muffled/underwater-audio-on-android-hint-its-chatgpt/</link><description>&lt;p&gt;My new Nothing Ear earphones had been working great, but after a few weeks I noticed that sometimes they sounded like they were underwater. The audio would be extremely quiet and muffled. No matter how I adjusted any of the EQ settings, noise cancellation, in-ear detection, codec choice, or anything else in the Nothing app, the problem persisted. A full reset of the earphones didn&amp;rsquo;t help. I reached out to Nothing support and heard crickets to this day and decided I&amp;rsquo;d give it a week or two more before returning them.&lt;/p&gt;</description><author>Liza Shulyayeva</author><pubDate>Sat, 02 Nov 2024 23:01:33 GMT</pubDate><guid isPermaLink="true">https://liza.io/how-i-fixed-my-nothing-ear-muffled/underwater-audio-on-android-hint-its-chatgpt/</guid></item><item><title>What if you hadn't?</title><link>https://martinrue.com/what-if-you-hadn't</link><description>"Had I not..." is a calling. It's a manifesto of living in every direction at the same time, driven by curiosity and a desire to learn and experience as many things as possible.</description><author>Martin Rue</author><pubDate>Sat, 02 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/what-if-you-hadn't</guid></item><item><title>A fascinating attempt at Steam phishing</title><link>https://utf9k.net/blog/fascinating-steam-scam/</link><description>I enjoyed almost being tricked but really shouldn't</description><author>utf9k</author><pubDate>Sat, 02 Nov 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://utf9k.net/blog/fascinating-steam-scam/</guid></item><item><title>Models Writing About Coding With Models</title><link>https://www.danielcorin.com/posts/2024/models-writing-about-coding-with-models/</link><description>Models Writing About Coding With Models</description><author>Thought Eddies</author><pubDate>Sat, 02 Nov 2024 03:26:30 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2024/models-writing-about-coding-with-models/</guid></item><item><title>Breaking CityHash64, MurmurHash2/3, wyhash, and more...</title><link>https://orlp.net/blog/breaking-hash-functions/</link><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Hash_function"&gt;Hash functions&lt;/a&gt; are incredibly
neat mathematical objects. They can map arbitrary data to a small fixed-size
output domain such that the mapping is deterministic, yet appears to be random.
This “deterministic randomness” is incredibly useful for a variety of purposes,
such as &lt;a href="https://en.wikipedia.org/wiki/Hash_function"&gt;hash tables&lt;/a&gt;,
&lt;a href="https://en.wikipedia.org/wiki/Checksum"&gt;checksums&lt;/a&gt;, &lt;a href="https://en.wikipedia.org/wiki/Monte_Carlo_algorithm"&gt;monte carlo
algorithms&lt;/a&gt;,
communication-less &lt;a href="https://en.wikipedia.org/wiki/Distributed_algorithm"&gt;distributed
algorithms&lt;/a&gt;, etc, the list
goes on.&lt;/p&gt;
&lt;p&gt;In this article we will take a look at the dark side of hash functions: when
things go wrong. Luckily this essentially never happens due to unlucky inputs in
the wild (for good hash functions, at least). However, people exist, and some of
them may be malicious. Thus we must look towards computer security for answers.
I will quickly explain some of the basics of hash function security and then
show how easy it is to break this security for some commonly used
non-cryptographic hash functions.&lt;/p&gt;
&lt;p&gt;As a teaser, this article explains how you can generate strings
such as these, thousands per second:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt; cityhash64(&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;orlp-cityhash64-D-:K5yx*zkgaaaaa&amp;quot;&lt;/span&gt;&lt;span&gt;) == &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1337
&lt;/span&gt;&lt;span&gt;murmurhash2(&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;orlp-murmurhash64-bkiaaa&amp;amp;JInaNcZ&amp;quot;&lt;/span&gt;&lt;span&gt;) == &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1337
&lt;/span&gt;&lt;span&gt;murmurhash3(&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;orlp-murmurhash3_x86_32-haaaPa*+&amp;quot;&lt;/span&gt;&lt;span&gt;) == &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1337
&lt;/span&gt;&lt;span&gt; farmhash64(&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;orlp-farmhash64-/v^CqdPvziuheaaa&amp;quot;&lt;/span&gt;&lt;span&gt;) == &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1337
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I also show how you can create some really funky pairs of strings that can be
concatenated arbitrarily such that when concatenating $k$ strings together
any of the $2^k$ combinations all have the same hash output, regardless of the
seed used for the hash function:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;a = &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;xx0rlpx!xxsXъВ&amp;quot;
&lt;/span&gt;&lt;span&gt;b = &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;xxsXъВxx0rlpx!&amp;quot;
&lt;/span&gt;&lt;span&gt;murmurhash2(a + a, seed) == murmurhash2(a + b, seed)
&lt;/span&gt;&lt;span&gt;murmurhash2(a + a, seed) == murmurhash2(b + a, seed)
&lt;/span&gt;&lt;span&gt;murmurhash2(a + a, seed) == murmurhash2(b + b, seed)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;a = &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;!&amp;amp;orlpՓ&amp;quot;
&lt;/span&gt;&lt;span&gt;b = &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;yǏglp$X&amp;quot;
&lt;/span&gt;&lt;span&gt;murmurhash3(a + a, seed) == murmurhash3(a + b, seed)
&lt;/span&gt;&lt;span&gt;murmurhash3(a + a, seed) == murmurhash3(b + a, seed)
&lt;/span&gt;&lt;span&gt;murmurhash3(a + a, seed) == murmurhash3(b + b, seed)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="hash-function-security-basics"&gt;&lt;a class="anchor" href="#hash-function-security-basics"&gt;Hash function security basics&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Hash functions play a critical role in computer security. Hash
functions are used not only to verify messages over secure channels, they are
also used to identify trusted updates as well as known viruses. Virtually every
signature scheme ever used starts with a hash function.&lt;/p&gt;
&lt;p&gt;If a hash function does not behave randomly, we can break the above security
constructs. &lt;a href="https://en.wikipedia.org/wiki/Cryptographic_hash_function"&gt;Cryptographic hash
functions&lt;/a&gt; thus take
the randomness aspect very seriously. The ideal hash function would choose an
output completely at random for each input, remembering that choice for future
calls. This is called a &lt;a href="https://en.wikipedia.org/wiki/Random_oracle"&gt;random
oracle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The problem is that a random oracle requires a true random number generator, and
more problematically, a globally accessible infinite memory bank. So we
approximate it using deterministic hash functions instead. These compute their
output by essentially shuffling their input really, really well, in such a way
that it is not feasible to reverse.&lt;/p&gt;
&lt;p&gt;To help quantify whether a specific function does a good job of approximating a
random oracle, cryptographers came up with a variety of properties that a random
oracle would have. The three most important and well-known properties a secure
cryptographic hash function should satisfy are:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pre-image resistance.&lt;/strong&gt; For some constant $c$ it should be hard to find
some input $m$ such that $h(m) = c$.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Second pre-image resistance.&lt;/strong&gt; For some input $m_1$ it should be hard to
find another input $m_2$ such that $h(m_1) = h(m_2)$.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Collision resistance.&lt;/strong&gt; It should be hard to find inputs $m_1, m_2$ such
that $h(m_1) = h(m_2)$.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;aside&gt;Note that collision resistance implies second pre-image resistance
which in turn implies pre-image resistance. Conversely, a pre-image attack breaks all three
properties.&lt;/aside&gt;
&lt;p&gt;We generally consider one of these properties &lt;em&gt;broken&lt;/em&gt; if there exists a method
that produces a collision or pre-image faster than simply trying random
inputs (also known as a &lt;em&gt;brute force attack&lt;/em&gt;). However, there are definitely
gradations in breakage, as some methods are only several orders of magnitude
faster than brute force. That may sound like a lot, but a method taking
$2^{110}$ steps instead of $2^{128}$ are still both equally out of reach for
today’s computers.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/MD5"&gt;MD5&lt;/a&gt; used to be a common hash function, and
&lt;a href="https://en.wikipedia.org/wiki/SHA-1"&gt;SHA-1&lt;/a&gt; is still in common use today. While
both were considered cryptographically secure at one point, generating MD5
collisions now takes less than a second on a modern PC. In 2017 a collaboration
of researchers from CWI and Google and announced &lt;a href="https://shattered.io/"&gt;the first SHA-1
collision&lt;/a&gt;. However, as far as I’m aware, neither MD5 nor
SHA-1 have practical (second) pre-image attacks, only theoretical ones.&lt;/p&gt;
&lt;h2 id="non-cryptographic-hash-functions"&gt;&lt;a class="anchor" href="#non-cryptographic-hash-functions"&gt;Non-cryptographic hash functions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Cryptographically secure hash functions tend to have a small problem: they’re
slow. Modern hash functions such as &lt;a href="https://github.com/BLAKE3-team/BLAKE3"&gt;BLAKE3&lt;/a&gt;
resolve this somewhat by heavily vectorizing the hash using
SIMD instructions, as well as parallelizing over multiple threads, but even then
they require large input sizes before reaching those speeds.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;One particular use-case for hash functions is deriving a secret key from a
password: a &lt;a href="https://en.wikipedia.org/wiki/Key_derivation_function"&gt;key derivation
function&lt;/a&gt;. Unlike regular
hash functions, being slow is actually a safety feature here to protect against
brute forcing passwords. Modern ones such as
&lt;a href="https://en.wikipedia.org/wiki/Argon2"&gt;Argon2&lt;/a&gt; also intentionally use a lot of
memory for protection against specialized hardware such as &lt;a href="https://en.wikipedia.org/wiki/Application-specific_integrated_circuit"&gt;ASICs&lt;/a&gt;
or &lt;a href="https://en.wikipedia.org/wiki/Field-programmable_gate_array"&gt;FPGAs&lt;/a&gt;.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;A lot of problems don’t necessarily require secure hash functions, and people
would much prefer a faster hash speed. Especially when we are computing many
small hashes, such as in a &lt;a href="https://en.wikipedia.org/wiki/Hash_table"&gt;hash table&lt;/a&gt;.
Let’s take a look what common hash table implementations actually use as their
hash for strings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;C++: there are multiple standard library implementations, but 64-bit
&lt;code&gt;clang&lt;/code&gt; 13.0.0 on Apple M1 &lt;a href="https://github.com/llvm-mirror/libcxx/blob/78d6a7767ed57b50122a161b91f59f19c9bd0d19/include/utility#L977"&gt;ships&lt;/a&gt; &lt;a href="https://github.com/google/cityhash"&gt;&lt;code&gt;CityHash64&lt;/code&gt;&lt;/a&gt;.
Currently &lt;code&gt;libstdc++&lt;/code&gt; &lt;a href="https://github.com/gcc-mirror/gcc/blob/20d790aa3ea5b0d240032cab997b8e0938cac62c/libstdc%2B%2B-v3/libsupc%2B%2B/hash_bytes.cc#L136"&gt;ships&lt;/a&gt;
&lt;a href="https://github.com/aappleby/smhasher/blob/master/src/MurmurHash2.cpp"&gt;&lt;code&gt;MurmurHash64A&lt;/code&gt;&lt;/a&gt;,
a variant of Murmur2 for 64-bit platforms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Java: OpenJDK uses an &lt;a href="https://github.com/openjdk/zgc/blob/ccf2f5837b31cddd24ec81f7f67107d9fc03c294/src/java.base/share/classes/jdk/internal/util/ArraysSupport.java#L212"&gt;incredibly simple hash algorithm&lt;/a&gt;, which essentially just computes
&lt;code&gt;h = 31 * h + c&lt;/code&gt; for each character &lt;code&gt;c&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;PHP: the Zend engine uses &lt;a href="https://github.com/php/php-src/blob/master/Zend/zend_string.h#L431"&gt;essentially the same algorithm&lt;/a&gt;
as Java, just using unsigned integers and &lt;code&gt;33&lt;/code&gt; as its multiplier.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Nim: it &lt;a href="https://github.com/nim-lang/Nim/blob/46d2161c23c2aa1905571512b9a1ef7d61ae670e/lib/pure/hashes.nim#L386"&gt;used to use&lt;/a&gt; &lt;a href="https://github.com/PeterScott/murmur3/blob/master/murmur3.c"&gt;&lt;code&gt;MurmurHash3_x86_32&lt;/code&gt;&lt;/a&gt;. While writing this article they appeared to &lt;a href="https://github.com/nim-lang/Nim/blob/46bb47a444bd377860d832fc1c62b262343f36a2/lib/pure/hashes.nim#L537"&gt;have switched&lt;/a&gt; to use
&lt;a href="https://github.com/google/farmhash"&gt;farmhash&lt;/a&gt; by default.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Zig: it &lt;a href="https://github.com/ziglang/zig/blob/904f414e7eab7bc0f7ea00f616831bfc3c1f18a4/lib/std/hash_map.zig#L31"&gt;uses&lt;/a&gt;
&lt;a href="https://github.com/wangyi-fudan/wyhash/blob/master/wyhash.h"&gt;&lt;code&gt;wyhash&lt;/code&gt;&lt;/a&gt; by default, with &lt;code&gt;0&lt;/code&gt; as seed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Javascript: in V8 they use &lt;a href="https://github.com/v8/v8/blob/b3776d5dea2f7858e9903a014b63ea86ef30c04f/src/strings/string-hasher-inl.h#L114"&gt;a custom&lt;/a&gt;
weak string hash, with a randomly initialized seed.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;There were some that used stronger hashes by default as well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Go &lt;a href="https://github.com/golang/go/blob/d12fe60004ae5e4024c8a93f4f7de7183bb61576/src/runtime/asm_amd64.s#L1117"&gt;uses&lt;/a&gt; an
&lt;a href="https://en.wikipedia.org/wiki/Advanced_Encryption_Standard"&gt;AES&lt;/a&gt;-based hash
if hardware acceleration is available on x86-64. Even though its construction is custom
and likely not full-strength cryptographically secure, breaking it is too
much effort and quite possibly beyond my capabilities.&lt;/p&gt;
&lt;p&gt;If not available, it uses an algorithm &lt;a href="https://github.com/golang/go/blob/d12fe60004ae5e4024c8a93f4f7de7183bb61576/src/runtime/hash64.go#L25"&gt;inspired by wyhash&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Python and Rust use &lt;a href="https://en.wikipedia.org/wiki/SipHash"&gt;SipHash&lt;/a&gt; by
default, which is a cryptographically secure &lt;a href="https://en.wikipedia.org/wiki/Pseudorandom_function_family"&gt;pseudorandom function&lt;/a&gt;.
This is effectively a hash function where you’re allowed to use a secret key
during hashing, unlike a hash like SHA-2 where everyone knows all information
involved.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This latter concept is actually really important, at least for protecting
against &lt;a href="https://en.wikipedia.org/wiki/Collision_attack"&gt;HashDoS&lt;/a&gt; in hash
tables. Even if a hash function is perfectly secure over its complete output,
hash tables further reduce the output to only a couple bits to find the data it
is looking for. For a static hash function without any randomness it’s possible
to produce large lists of hashes that collide post-reduction, just by brute
force. But for non-cryptographic hashes as we’ll see here we often don’t need
brute force and can generate collisions at high speed for the full output, if
not randomized by a random seed.&lt;/p&gt;
&lt;h2 id="interlude-inverse-operations"&gt;&lt;a class="anchor" href="#interlude-inverse-operations"&gt;Interlude: inverse operations&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before we get to breaking some of the above hash functions, I must explain a basic
technique I will use a lot: the inverting of operations. We are first exposed to
this in primary school, where we might get faced by a question such as “$2 + x = 10$”.
There we learn &lt;em&gt;subtraction&lt;/em&gt; is the &lt;em&gt;inverse&lt;/em&gt; of addition, such that we may find
$x$ by computing $10 - 2 = 8$.&lt;/p&gt;
&lt;p&gt;Most operations on the integer registers in computers are also invertible, despite
the integers being reduced modulo $2^{w}$ in the case of overflow. Let
us study some:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Addition can be inverted using subtraction. That is, &lt;code&gt;x += y&lt;/code&gt; can be inverted
using &lt;code&gt;x -= y&lt;/code&gt;. Seems obvious enough.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Multiplication by a constant $c$ is &lt;em&gt;not&lt;/em&gt; inverted by division. This would
not work in the case of overflow. Instead, we calculate the &lt;a href="https://en.wikipedia.org/wiki/Modular_multiplicative_inverse"&gt;modular
multiplicative
inverse&lt;/a&gt; of
$c$. This is an integer $c^{-1}$ such that $c \cdot c^{-1} \equiv 1 \pmod
{m}$. Then we invert multiplication by $c$ simply by multiplying by $c^{-1}$.&lt;/p&gt;
&lt;p&gt;This constant exists if and only if $c$ is &lt;a href="https://en.wikipedia.org/wiki/Coprime_integers"&gt;coprime&lt;/a&gt; with our modulus $m$, which for
us means that $c$ must be odd as $m = 2^n$. For example, multiplication by $2$ is not
invertible, which is easy to see as such, as it is equivalent to a bit shift
to the left by one position, losing the most significant bit forever.&lt;/p&gt;
&lt;p&gt;Without delving into the details, here is a snippet of Python code that computes
the modular multiplicative inverse of an integer using the
&lt;a href="https://en.wikipedia.org/wiki/Extended_Euclidean_algorithm"&gt;extended Euclidean algorithm&lt;/a&gt;
by calculating $x, y$ such that
$$cx + my = \gcd(c, m).$$
Then, because $c$ is coprime we find $\gcd(c, m) = 1$, which means that
$$cx + 0 \equiv 1 \pmod m,$$
and thus $x = c^{-1}$.&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span style="color: #215da8;"&gt;def &lt;/span&gt;&lt;span&gt;egcd(a, b):
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;if &lt;/span&gt;&lt;span&gt;a == &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;(b, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;    g, y, x = egcd(b % a, a)
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;(g, x - (b // a) * y, y)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;def &lt;/span&gt;&lt;span&gt;modinv(c, m):
&lt;/span&gt;&lt;span&gt;    g, x, y = egcd(c, m)
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;assert &lt;/span&gt;&lt;span&gt;g == &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;c, m must be coprime&amp;quot;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;x % m
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Using this we can invert modular multiplication:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; modinv(&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;17&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;2&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;32&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4042322161
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;42 &lt;/span&gt;&lt;span&gt;* &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;17 &lt;/span&gt;&lt;span&gt;* &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4042322161 &lt;/span&gt;&lt;span&gt;% &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;2&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;32
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;42
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Magic!&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;XOR can be inverted using… XOR. It is its own inverse. So &lt;code&gt;x ^= y&lt;/code&gt; can be
inverted using &lt;code&gt;x ^= y&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Bit shifts can not be inverted, but two common operations in hash functions
that use bit shifts can be. The first is bit &lt;em&gt;rotation&lt;/em&gt; by a constant. This
is best explained visually, for example a bit rotation to the left by 3
places on a 8-bit word, where each bit is shown as a letter:&lt;/p&gt;
&lt;pre style="background-color: #ffffff; color: #202020;"&gt;&lt;code&gt;&lt;span&gt;abcdefghi
&lt;/span&gt;&lt;span&gt;defghiabc
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The formula for a right-rotation of &lt;code&gt;k&lt;/code&gt; places is &lt;code&gt;(x &amp;gt;&amp;gt; k) | (x &amp;lt;&amp;lt; (w - k))&lt;/code&gt;, where &lt;code&gt;w&lt;/code&gt; is the width of the integer type. Its inverse is a
left-rotation, which simply swaps the direction of both shifts.
Alternatively, the inverse of a right-rotation of &lt;code&gt;k&lt;/code&gt; places is another
right-rotation of &lt;code&gt;w-k&lt;/code&gt; places.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Another common operation in hash functions is the “xorshift”. It is an operation
of one of the following forms, with $k &amp;gt; 0$:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;x ^= x &amp;lt;&amp;lt; k  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Left xorshift.
&lt;/span&gt;&lt;span&gt;x ^= x &amp;gt;&amp;gt; k  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Right xorshift.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;How to invert it is entirely analogous between the two, so I will focus on the
left xorshift.&lt;/p&gt;
&lt;p&gt;An important observation is that the least
significant $k$ bits are left entirely untouched by the xorshift.
Thus by repeating the operation, we recover the least significant $2k$ bits,
as the XOR will invert itself for the next $k$ bits.
Let’s take a look at the resulting value to see how we should proceed:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;v0 = (x &amp;lt;&amp;lt; k) ^ x
&lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Apply first step of inverse v1 = v0 ^ (v0 &amp;lt;&amp;lt; k).
&lt;/span&gt;&lt;span&gt;v1 = (x &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;2&lt;/span&gt;&lt;span&gt;*k) ^ (x &amp;lt;&amp;lt; k) ^ (x &amp;lt;&amp;lt; k) ^ x
&lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Simplify using self-inverse (x &amp;lt;&amp;lt; k) ^ (x &amp;lt;&amp;lt; k) = 0.
&lt;/span&gt;&lt;span&gt;v1 = (x &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;2&lt;/span&gt;&lt;span&gt;*k) ^ x
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;From this we can conclude the following identity:
$$\operatorname{xorshift}(\operatorname{xorshift}(x, k), k) = \operatorname{xorshift}(x, 2k)$$
Now we only need one more observation to complete our algorithm: a xorshift of $k \geq w$ where $w$ is the width of our integer is
a no-op. Thus we repeatedly apply our doubling identity until we reach
large enough $q$ such that $\operatorname{xorshift}(x, 2^q \cdot k) = x$.&lt;/p&gt;
&lt;p&gt;For example, to invert a left xorshift by 13 for 64-bit integers we apply the following sequence:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;x ^= x &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;13  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Left xorshift by 13.
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;x ^= x &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;13  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Inverse step 1.
&lt;/span&gt;&lt;span&gt;x ^= x &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;26  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Inverse step 2.
&lt;/span&gt;&lt;span&gt;x ^= x &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;52  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Inverse step 3.
&lt;/span&gt;&lt;span style="color: #76647b;"&gt;// x ^= x &amp;lt;&amp;lt; 104  // Next step would be a no-op.
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Armed with this knowledge, we can now attack.&lt;/p&gt;
&lt;h2 id="breaking-cityhash64"&gt;&lt;a class="anchor" href="#breaking-cityhash64"&gt;Breaking &lt;code&gt;CityHash64&lt;/code&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let us take a look at (part of) &lt;a href="https://github.com/llvm-mirror/libcxx/blob/78d6a7767ed57b50122a161b91f59f19c9bd0d19/include/utility#L977"&gt;the source code&lt;/a&gt; of
&lt;code&gt;CityHash64&lt;/code&gt; from &lt;code&gt;libcxx&lt;/code&gt; that’s used for hashing strings on 64-bit platforms:&lt;/p&gt;
&lt;aside&gt;C++ standard library code goes through a process known as 'uglification',
which prepends underscores to all identifiers. This is because those identifiers
are reserved by the standard to only be used in the standard library, and thus
won't be replaced by macros from standards-compliant code. For your sanity's
sake I removed them here.&lt;/aside&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;static const uint64_t mul = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0x9ddfea08eb382d69&lt;/span&gt;&lt;span style="color: #215da8;"&gt;ULL&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;static const uint64_t k0 = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xc3a5c85c97cb3127&lt;/span&gt;&lt;span style="color: #215da8;"&gt;ULL&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;static const uint64_t k1 = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xb492b66fbe98f273&lt;/span&gt;&lt;span style="color: #215da8;"&gt;ULL&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;static const uint64_t k2 = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0x9ae16a3b2f90404f&lt;/span&gt;&lt;span style="color: #215da8;"&gt;ULL&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;static const uint64_t k3 = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xc949d7c7509e6557&lt;/span&gt;&lt;span style="color: #215da8;"&gt;ULL&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;template&lt;/span&gt;&lt;span&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #215da8;"&gt;class&lt;/span&gt;&lt;span&gt; T&amp;gt;
&lt;/span&gt;&lt;span&gt;T loadword(const &lt;/span&gt;&lt;span style="color: #215da8;"&gt;void&lt;/span&gt;&lt;span&gt;* p) {
&lt;/span&gt;&lt;span&gt;    T r;
&lt;/span&gt;&lt;span&gt;    std::memcpy(&amp;amp;r, p, sizeof(r));
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return&lt;/span&gt;&lt;span&gt; r;
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;uint64_t rotate(uint64_t val, &lt;/span&gt;&lt;span style="color: #215da8;"&gt;int &lt;/span&gt;&lt;span&gt;shift) {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;if &lt;/span&gt;&lt;span&gt;(shift == &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return&lt;/span&gt;&lt;span&gt; val;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;(val &amp;gt;&amp;gt; shift) | (val &amp;lt;&amp;lt; (&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;64 &lt;/span&gt;&lt;span&gt;- shift));
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;uint64_t hash_len_16(uint64_t u, uint64_t v) {
&lt;/span&gt;&lt;span&gt;    uint64_t x = u ^ v;
&lt;/span&gt;&lt;span&gt;    x *= mul;
&lt;/span&gt;&lt;span&gt;    x ^= x &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    uint64_t y = v ^ x;
&lt;/span&gt;&lt;span&gt;    y *= mul;
&lt;/span&gt;&lt;span&gt;    y ^= y &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    y *= mul;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return&lt;/span&gt;&lt;span&gt; y;
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;uint64_t hash_len_17_to_32(const &lt;/span&gt;&lt;span style="color: #215da8;"&gt;char &lt;/span&gt;&lt;span&gt;*s, uint64_t len) {
&lt;/span&gt;&lt;span&gt;    const uint64_t a = loadword&amp;lt;uint64_t&amp;gt;(s) * k1;
&lt;/span&gt;&lt;span&gt;    const uint64_t b = loadword&amp;lt;uint64_t&amp;gt;(s + &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;8&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;    const uint64_t c = loadword&amp;lt;uint64_t&amp;gt;(s + len - &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;8&lt;/span&gt;&lt;span&gt;) * k2;
&lt;/span&gt;&lt;span&gt;    const uint64_t d = loadword&amp;lt;uint64_t&amp;gt;(s + len - &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;16&lt;/span&gt;&lt;span&gt;) * k0;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;hash_len_16(
&lt;/span&gt;&lt;span&gt;        rotate(a - b, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;43&lt;/span&gt;&lt;span&gt;) + rotate(c, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;30&lt;/span&gt;&lt;span&gt;) + d,
&lt;/span&gt;&lt;span&gt;        a + rotate(b ^ k3, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;20&lt;/span&gt;&lt;span&gt;) - c + len
&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To break this, let’s assume we’ll always give length 32 inputs. Then the
implementation will always call &lt;code&gt;hash_len_17_to_32&lt;/code&gt;, and we have full control
over variables &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt;, &lt;code&gt;c&lt;/code&gt; and &lt;code&gt;d&lt;/code&gt; by changing our input.&lt;/p&gt;
&lt;p&gt;Note that &lt;code&gt;d&lt;/code&gt; is only used once, in the final expression. This makes it a
prime target for attacking the hash. We will choose &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt; arbitrarily,
and then solve for &lt;code&gt;d&lt;/code&gt; to compute a desired hash outcome.&lt;/p&gt;
&lt;p&gt;Using the above &lt;code&gt;modinv&lt;/code&gt; function we first compute the necessary modular multiplicative
inverses of &lt;code&gt;mul&lt;/code&gt; and &lt;code&gt;k0&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0x9ddfea08eb382d69 &lt;/span&gt;&lt;span&gt;* &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xdc56e6f5090b32d9 &lt;/span&gt;&lt;span&gt;% &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;2&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;64
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xc3a5c85c97cb3127 &lt;/span&gt;&lt;span&gt;* &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0x81bc9c5aa9c72e97 &lt;/span&gt;&lt;span&gt;% &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;2&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;64
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also note that in this case the xorshift is easy to invert, as &lt;code&gt;x ^= x &amp;gt;&amp;gt; 47&lt;/code&gt;
is simply its own inverse. Having all the components ready, we can invert
the function step by step.&lt;/p&gt;
&lt;p&gt;We first load &lt;code&gt;a&lt;/code&gt;, &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt; like in the hash function, and compute&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;uint64_t v = a + rotate(b ^ k3, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;20&lt;/span&gt;&lt;span&gt;) - c + len;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;which is the second parameter to &lt;code&gt;hash_len_16&lt;/code&gt;. Then, starting from our
desired return value of &lt;code&gt;hash_len_16(u, v)&lt;/code&gt; we work backwards step by step, inverting
each operation to find the function argument &lt;code&gt;u&lt;/code&gt; that would result in our target &lt;code&gt;hash&lt;/code&gt;.
Then once we have found such the unique &lt;code&gt;u&lt;/code&gt; we compute our required input &lt;code&gt;d&lt;/code&gt;.
Putting it all together:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;static const uint64_t mul_inv = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xdc56e6f5090b32d9&lt;/span&gt;&lt;span style="color: #215da8;"&gt;ULL&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;static const uint64_t k0_inv  = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0x81bc9c5aa9c72e97&lt;/span&gt;&lt;span style="color: #215da8;"&gt;ULL&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #215da8;"&gt;void &lt;/span&gt;&lt;span&gt;cityhash64_preimage32(uint64_t hash, &lt;/span&gt;&lt;span style="color: #215da8;"&gt;char &lt;/span&gt;&lt;span&gt;*s) {
&lt;/span&gt;&lt;span&gt;    const uint64_t len = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;32&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    const uint64_t a = loadword&amp;lt;uint64_t&amp;gt;(s) * k1;
&lt;/span&gt;&lt;span&gt;    const uint64_t b = loadword&amp;lt;uint64_t&amp;gt;(s + &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;8&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;    const uint64_t c = loadword&amp;lt;uint64_t&amp;gt;(s + len - &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;8&lt;/span&gt;&lt;span&gt;) * k2;
&lt;/span&gt;&lt;span&gt;    
&lt;/span&gt;&lt;span&gt;    uint64_t v = a + rotate(b ^ k3, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;20&lt;/span&gt;&lt;span&gt;) - c + len;
&lt;/span&gt;&lt;span&gt;    
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Invert hash_len_16(u, v). Original operation inverted
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// at each step is shown on the right, note that it is in
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// the inverse order of hash_len_16.
&lt;/span&gt;&lt;span&gt;    uint64_t y = hash;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// return y;
&lt;/span&gt;&lt;span&gt;    y *= mul_inv;         &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// y *= mul;
&lt;/span&gt;&lt;span&gt;    y ^= y &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;         &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// y ^= y &amp;gt;&amp;gt; 47;
&lt;/span&gt;&lt;span&gt;    y *= mul_inv;         &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// y *= mul;
&lt;/span&gt;&lt;span&gt;    uint64_t x = y ^ v;   &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// uint64_t y = v ^ x;
&lt;/span&gt;&lt;span&gt;    x ^= x &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;         &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// x ^= x &amp;gt;&amp;gt; 47;
&lt;/span&gt;&lt;span&gt;    x *= mul_inv;         &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// x *= mul;
&lt;/span&gt;&lt;span&gt;    uint64_t u = x ^ v;   &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// uint64_t x = u ^ v;
&lt;/span&gt;&lt;span&gt;    
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Find loadword&amp;lt;uint64_t&amp;gt;(s + len - 16).
&lt;/span&gt;&lt;span&gt;    uint64_t d = u - rotate(a - b, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;43&lt;/span&gt;&lt;span&gt;) - rotate(c, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;30&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;    d *= k0_inv;
&lt;/span&gt;&lt;span&gt;    std::memcpy(s + len - &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;16&lt;/span&gt;&lt;span&gt;, &amp;amp;d, sizeof(d));
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The chance that a random &lt;code&gt;uint64_t&lt;/code&gt; forms 8 printable ASCII bytes is
$\left(94/256\right)^8 \approx 0.033%$. Not great, but &lt;code&gt;cityhash64_preimage32&lt;/code&gt;
is so fast that having to repeat it on average ~3000 times to get a purely
ASCII result isn’t so bad.&lt;/p&gt;
&lt;p&gt;For example, the following 10 strings all hash to &lt;code&gt;1337&lt;/code&gt; using CityHash64, generated
using &lt;a href="https://gist.github.com/orlp/8debf0047e7735b43887aafb041c9a01"&gt;this code&lt;/a&gt;:&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;I’ve noticed there’s variants of CityHash64 with subtle differences in the wild. I chose to
attack the variant shipped with &lt;code&gt;libc++&lt;/code&gt;, so it should work for &lt;code&gt;std::hash&lt;/code&gt; there, for example.
I also assume a little-endian machine throughout this article, your mileage
may vary on a big-endian machine depending on the hash implementation.&lt;/p&gt;
&lt;/aside&gt;
&lt;pre style="background-color: #ffffff; color: #202020;"&gt;&lt;code&gt;&lt;span&gt;orlp-cityhash64-D-:K5yx*zkgaaaaa
&lt;/span&gt;&lt;span&gt;orlp-cityhash64-TXb7;1j&amp;amp;btkaaaaa
&lt;/span&gt;&lt;span&gt;orlp-cityhash64-+/LM$0 ;msnaaaaa
&lt;/span&gt;&lt;span&gt;orlp-cityhash64-u'f&amp;amp;&amp;gt;I'~mtnaaaaa
&lt;/span&gt;&lt;span&gt;orlp-cityhash64-pEEv.LyGcnpaaaaa
&lt;/span&gt;&lt;span&gt;orlp-cityhash64-v~~bm@,Vahtaaaaa
&lt;/span&gt;&lt;span&gt;orlp-cityhash64-RxHr_&amp;amp;~{miuaaaaa
&lt;/span&gt;&lt;span&gt;orlp-cityhash64-is_$34#&amp;gt;uavaaaaa
&lt;/span&gt;&lt;span&gt;orlp-cityhash64-$*~l\{S!zoyaaaaa
&lt;/span&gt;&lt;span&gt;orlp-cityhash64-W@^5|3^:gtcbaaaa
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="breaking-murmurhash2"&gt;&lt;a class="anchor" href="#breaking-murmurhash2"&gt;Breaking MurmurHash2&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We can’t let &lt;code&gt;libstdc++&lt;/code&gt; get away after targetting &lt;code&gt;libc++&lt;/code&gt;, can we?
The &lt;a href="https://github.com/gcc-mirror/gcc/blob/97a36b466ba1420210294f0a1dd7002054ba3b7e/libstdc%2B%2B-v3/include/bits/basic_string.h#L4402"&gt;default string hash&lt;/a&gt;
&lt;a href="https://github.com/gcc-mirror/gcc/blob/97a36b466ba1420210294f0a1dd7002054ba3b7e/libstdc%2B%2B-v3/include/bits/functional_hash.h#L206"&gt;calls&lt;/a&gt;
an &lt;a href="https://github.com/gcc-mirror/gcc/blob/97a36b466ba1420210294f0a1dd7002054ba3b7e/libstdc%2B%2B-v3/libsupc%2B%2B/hash_bytes.cc#L138"&gt;implementation of MurmurHash2&lt;/a&gt;
with seed &lt;code&gt;0xc70f6907&lt;/code&gt;. The hash—simplified to only handle strings whose
lengths are multiples of 8—is as follows:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;uint64_t murmurhash64a(const &lt;/span&gt;&lt;span style="color: #215da8;"&gt;char&lt;/span&gt;&lt;span&gt;* s, size_t len, uint64_t seed) {
&lt;/span&gt;&lt;span&gt;    const uint64_t mul = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xc6a4a7935bd1e995&lt;/span&gt;&lt;span style="color: #215da8;"&gt;ULL&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    uint64_t hash = seed ^ (len * mul);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;for &lt;/span&gt;&lt;span&gt;(const &lt;/span&gt;&lt;span style="color: #215da8;"&gt;char&lt;/span&gt;&lt;span&gt;* p = s; p != s + len; p += &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;8&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;        uint64_t data = loadword&amp;lt;uint64_t&amp;gt;(p);
&lt;/span&gt;&lt;span&gt;        data *= mul;
&lt;/span&gt;&lt;span&gt;        data ^= data &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;        data *= mul;
&lt;/span&gt;&lt;span&gt;        hash ^= data;
&lt;/span&gt;&lt;span&gt;        hash *= mul;
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    hash ^= hash &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    hash *= mul;
&lt;/span&gt;&lt;span&gt;    hash ^= hash &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return&lt;/span&gt;&lt;span&gt; hash;
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can take a similar approach here as before. We note that the modular
multiplicative inverse of &lt;code&gt;0xc6a4a7935bd1e995&lt;/code&gt; mod $2^{64}$ is
&lt;code&gt;0x5f7a0ea7e59b19bd&lt;/code&gt;. As an example, we can choose the first 24 bytes
arbitrarily, and solve for the last 8 bytes:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span style="color: #215da8;"&gt;void &lt;/span&gt;&lt;span&gt;murmurhash64a_preimage32(uint64_t hash, &lt;/span&gt;&lt;span style="color: #215da8;"&gt;char&lt;/span&gt;&lt;span&gt;* s, uint64_t seed) {
&lt;/span&gt;&lt;span&gt;    const uint64_t mul = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xc6a4a7935bd1e995&lt;/span&gt;&lt;span style="color: #215da8;"&gt;ULL&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    const uint64_t mulinv = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0x5f7a0ea7e59b19bd&lt;/span&gt;&lt;span style="color: #215da8;"&gt;ULL&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Compute the hash state for the first 24 bytes as normal.
&lt;/span&gt;&lt;span&gt;    uint64_t state = seed ^ (&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;32 &lt;/span&gt;&lt;span&gt;* mul);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;for &lt;/span&gt;&lt;span&gt;(const &lt;/span&gt;&lt;span style="color: #215da8;"&gt;char&lt;/span&gt;&lt;span&gt;* p = s; p != s + &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;24&lt;/span&gt;&lt;span&gt;; p += &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;8&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;        uint64_t data = loadword&amp;lt;uint64_t&amp;gt;(p);
&lt;/span&gt;&lt;span&gt;        data *= mul;
&lt;/span&gt;&lt;span&gt;        data ^= data &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;        data *= mul;
&lt;/span&gt;&lt;span&gt;        state ^= data;
&lt;/span&gt;&lt;span&gt;        state *= mul;
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;    
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Invert target hash transformation.
&lt;/span&gt;&lt;span&gt;                        &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// return hash;
&lt;/span&gt;&lt;span&gt;    hash ^= hash &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// hash ^= hash &amp;gt;&amp;gt; 47;
&lt;/span&gt;&lt;span&gt;    hash *= mulinv;     &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// hash *= mul;
&lt;/span&gt;&lt;span&gt;    hash ^= hash &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;; &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// hash ^= hash &amp;gt;&amp;gt; 47;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Invert last iteration for last 8 bytes.
&lt;/span&gt;&lt;span&gt;    hash *= mulinv;                &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// hash *= mul;
&lt;/span&gt;&lt;span&gt;    uint64_t data = state ^ hash;  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// hash = hash ^ data;
&lt;/span&gt;&lt;span&gt;    data *= mulinv;                &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// data *= mul;
&lt;/span&gt;&lt;span&gt;    data ^= data &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;            &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// data ^= data &amp;gt;&amp;gt; 47;
&lt;/span&gt;&lt;span&gt;    data *= mulinv;                &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// data *= mul;
&lt;/span&gt;&lt;span&gt;    std::memcpy(s + &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;24&lt;/span&gt;&lt;span&gt;, &amp;amp;data, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;8&lt;/span&gt;&lt;span&gt;); &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// data = loadword&amp;lt;uint64_t&amp;gt;(s);
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The following 10 strings all hash to &lt;code&gt;1337&lt;/code&gt; using MurmurHash64A with the
default seed &lt;code&gt;0xc70f6907&lt;/code&gt;, generated using &lt;a href="https://gist.github.com/orlp/59470263c1e2b05b035719f3121bcc45"&gt;this code&lt;/a&gt;:&lt;/p&gt;
&lt;pre style="background-color: #ffffff; color: #202020;"&gt;&lt;code&gt;&lt;span&gt;orlp-murmurhash64-bhbaaat;SXtgVa
&lt;/span&gt;&lt;span&gt;orlp-murmurhash64-bkiaaa&amp;amp;JInaNcZ
&lt;/span&gt;&lt;span&gt;orlp-murmurhash64-ewmaaa(%J+jw&amp;gt;j
&lt;/span&gt;&lt;span&gt;orlp-murmurhash64-vxpaaag&amp;quot;93\Yj5
&lt;/span&gt;&lt;span&gt;orlp-murmurhash64-ehuaaafa`Wp`/|
&lt;/span&gt;&lt;span&gt;orlp-murmurhash64-yizaaa1x.zQF6r
&lt;/span&gt;&lt;span&gt;orlp-murmurhash64-lpzaaaZphp&amp;amp;c F
&lt;/span&gt;&lt;span&gt;orlp-murmurhash64-wsjbaa771rz{z&amp;lt;
&lt;/span&gt;&lt;span&gt;orlp-murmurhash64-rnkbaazy4X]p&amp;gt;B
&lt;/span&gt;&lt;span&gt;orlp-murmurhash64-aqnbaaZ~OzP_Tp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id="universal-collision-attack-on-murmurhash64a"&gt;&lt;a class="anchor" href="#universal-collision-attack-on-murmurhash64a"&gt;Universal collision attack on MurmurHash64A&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In fact, MurmurHash64A is so weak that Jean-Philippe Aumasson, Daniel J.
Bernstein and Martin Boßlet published &lt;a href="https://cr.yp.to/talks/2012.12.29/slides.pdf"&gt;an
attack&lt;/a&gt; that creates sets of
strings which collide &lt;strong&gt;regardless of the random seed used&lt;/strong&gt;.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;To be fair to CityHash64… just kidding they found &lt;a href="http://web.archive.org/web/20140731141732/https://131002.net/siphash/citycollisions-20120730.tar.gz"&gt;universal collisions&lt;/a&gt;
against it as well, regardless of seed used.
CityHash64 is actually much easier to break in this way, as simply
doing the above pre-image attack targetting &lt;code&gt;0&lt;/code&gt; as hash
&lt;a href="https://github.com/google/cityhash/blob/f5dc54147fcce12cefd16548c8e760d68ac04226/src/city.cc#L410"&gt;makes the output purely dependent on the seed&lt;/a&gt;,
and thus a universal collision.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;To see how it works, let’s take a look at the core loop of MurmurHash64A:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;uint64_t data = loadword&amp;lt;uint64_t&amp;gt;(p);
&lt;/span&gt;&lt;span&gt;data *= mul;          &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Trivially invertible.
&lt;/span&gt;&lt;span&gt;data ^= data &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;   &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Trivially invertible.
&lt;/span&gt;&lt;span&gt;data *= mul;          &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Trivially invertible.
&lt;/span&gt;&lt;span&gt;state ^= data;
&lt;/span&gt;&lt;span&gt;state *= mul;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We know we can trivially invert the operations done on &lt;code&gt;data&lt;/code&gt; regardless of what the
current state is, so we might as well have had the following body:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;state ^= data;
&lt;/span&gt;&lt;span&gt;state *= mul;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the hash starts looking rather weak indeed. The clever trick they
employ is by creating two strings simultaneously, such that they
differ precisely in the top bit in each 8-byte word. Why the top bit?&lt;/p&gt;
&lt;pre class="language-python " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-python"&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1 &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;63
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;9223372036854775808
&lt;/span&gt;&lt;span&gt;&amp;gt;&amp;gt;&amp;gt; (&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1 &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;63&lt;/span&gt;&lt;span&gt;) * mul % &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;2&lt;/span&gt;&lt;span&gt;**&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;64
&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;9223372036854775808
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since &lt;code&gt;mul&lt;/code&gt; is odd, its least significant bit is set. Multiplying &lt;code&gt;1 &amp;lt;&amp;lt; 63&lt;/code&gt; by
it is equivalent to shifting that bit 63 places to the left, which is once again
&lt;code&gt;1 &amp;lt;&amp;lt; 63&lt;/code&gt;. That is, &lt;code&gt;1 &amp;lt;&amp;lt; 63&lt;/code&gt; is a fixed point for the &lt;code&gt;state *= mul&lt;/code&gt; operation.
We also note that for the top bit XOR is equivalent to addition, as the overflow
from addition is removed mod $2^{64}$.&lt;/p&gt;
&lt;p&gt;So if we have two input strings, one starting with the 8 bytes &lt;code&gt;data&lt;/code&gt;, and the
other starting with &lt;code&gt;data ^ (1 &amp;lt;&amp;lt; 63) == data + (1 &amp;lt;&amp;lt; 63)&lt;/code&gt; (after doing the
trivial inversions). We then find that the two states, regardless of seed,
differ exactly in the top bit after &lt;code&gt;state ^= data&lt;/code&gt;. After multiplication we
find we have two states &lt;code&gt;x * mul&lt;/code&gt; and &lt;code&gt;(x + (1 &amp;lt;&amp;lt; 63)) * mul == x * mul + (1 &amp;lt;&amp;lt; 63)&lt;/code&gt;… which again differ exactly in the top bit! We are now back to &lt;code&gt;state ^= data&lt;/code&gt; in our iteration, for the next 8 bytes. We can now use this moment to
cancel our top bit difference, by again feeding two 8-byte strings that
differ in the top bit (after inverting).&lt;/p&gt;
&lt;p&gt;In fact, we only have to find one pair of such strings that differ in the top
bit, which we can then repeat twice (in either order) to cancel our difference
again. When represented as a &lt;code&gt;uint64_t&lt;/code&gt; if we choose the first string as &lt;code&gt;x&lt;/code&gt; we
can derive the second string as&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;x *= mul;        &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Forward transformation...
&lt;/span&gt;&lt;span&gt;x ^= x &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// ...
&lt;/span&gt;&lt;span&gt;x *= mul;        &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// ...
&lt;/span&gt;&lt;span&gt;x ^= &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;1 &lt;/span&gt;&lt;span&gt;&amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;63&lt;/span&gt;&lt;span&gt;;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Difference in top bit.
&lt;/span&gt;&lt;span&gt;x *= mulinv;     &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Backwards transformation...
&lt;/span&gt;&lt;span&gt;x ^= x &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;47&lt;/span&gt;&lt;span&gt;;    &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// ...
&lt;/span&gt;&lt;span&gt;x *= mulinv;     &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// ...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I was unable to find a printable ASCII string that has another printable
ASCII string as its partner. But I was able to find the following pair of 8-byte
UTF-8 strings that differ in exactly the top bit after the Murmurhash64A input
transformation:&lt;/p&gt;
&lt;pre style="background-color: #ffffff; color: #202020;"&gt;&lt;code&gt;&lt;span&gt;xx0rlpx!
&lt;/span&gt;&lt;span&gt;xxsXъВ
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Combining them as such gives two 16-byte strings that when fed through the hash
algorithm manipulate the state in the same way: a collision.&lt;/p&gt;
&lt;pre style="background-color: #ffffff; color: #202020;"&gt;&lt;code&gt;&lt;span&gt;xx0rlpx!xxsXъВ
&lt;/span&gt;&lt;span&gt;xxsXъВxx0rlpx!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But it doesn’t stop there. By concatenating these two strings we can create
$2^n$ different colliding strings each $16n$ bytes long. With the current
&lt;code&gt;libstdc++&lt;/code&gt; implementation the following prints the same number eight times:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;std::hash&amp;lt;std::u8string&amp;gt; h;
&lt;/span&gt;&lt;span&gt;std::u8string a = &lt;/span&gt;&lt;span style="color: #215da8;"&gt;u8&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;xx0rlpx!xxsXъВ&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;std::u8string b = &lt;/span&gt;&lt;span style="color: #215da8;"&gt;u8&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;xxsXъВxx0rlpx!&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;std::cout &amp;lt;&amp;lt; h(a + a + a) &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;\n&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;std::cout &amp;lt;&amp;lt; h(a + a + b) &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;\n&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;std::cout &amp;lt;&amp;lt; h(a + b + a) &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;\n&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;std::cout &amp;lt;&amp;lt; h(a + b + b) &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;\n&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;std::cout &amp;lt;&amp;lt; h(b + a + a) &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;\n&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;std::cout &amp;lt;&amp;lt; h(b + a + b) &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;\n&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;std::cout &amp;lt;&amp;lt; h(b + b + a) &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;\n&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;std::cout &amp;lt;&amp;lt; h(b + b + b) &amp;lt;&amp;lt; &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;\n&lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even if the &lt;code&gt;libstdc++&lt;/code&gt; would randomize the seed used by MurmurHash64a, the
strings would &lt;em&gt;still&lt;/em&gt; collide.&lt;/p&gt;
&lt;h2 id="breaking-murmurhash3"&gt;&lt;a class="anchor" href="#breaking-murmurhash3"&gt;Breaking MurmurHash3&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nim &lt;del&gt;uses&lt;/del&gt;
&lt;a href="https://github.com/nim-lang/Nim/blob/46d2161c23c2aa1905571512b9a1ef7d61ae670e/lib/pure/hashes.nim#L386"&gt;used to use&lt;/a&gt;
&lt;a href="https://github.com/PeterScott/murmur3/blob/master/murmur3.c"&gt;&lt;code&gt;MurmurHash3_x86_32&lt;/code&gt;&lt;/a&gt;,
so let’s try to break that.&lt;/p&gt;
&lt;aside&gt;
Nim switched to use farmhash by default while I was procrastinating finishing this article.
Please pretend that it still uses MurmurHash3 while reading this section, so my words
make sense. Then, afterwards, we'll break farmhash too.
&lt;/aside&gt;
&lt;p&gt;If we once again simplify to strings whose lengths are a multiple of 4 we get the following code:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;uint32_t rotl32(uint32_t x, &lt;/span&gt;&lt;span style="color: #215da8;"&gt;int &lt;/span&gt;&lt;span&gt;r) {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;(x &amp;lt;&amp;lt; r) | (x &amp;gt;&amp;gt; (&lt;/span&gt;&lt;span style="color: #ae6018;"&gt;32 &lt;/span&gt;&lt;span&gt;- r));
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;uint32_t murmurhash3_x86_32(const &lt;/span&gt;&lt;span style="color: #215da8;"&gt;char&lt;/span&gt;&lt;span&gt;* s, &lt;/span&gt;&lt;span style="color: #215da8;"&gt;int &lt;/span&gt;&lt;span&gt;len, uint32_t seed) {
&lt;/span&gt;&lt;span&gt;    const uint32_t c1 = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xcc9e2d51&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    const uint32_t c2 = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0x1b873593&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    const uint32_t c3 = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0x85ebca6b&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    const uint32_t c4 = &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xc2b2ae35&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    uint32_t h = seed;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;for &lt;/span&gt;&lt;span&gt;(const &lt;/span&gt;&lt;span style="color: #215da8;"&gt;char&lt;/span&gt;&lt;span&gt;* p = s; p != s + len; p += &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;4&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;        uint32_t k = loadword&amp;lt;uint32_t&amp;gt;(p);
&lt;/span&gt;&lt;span&gt;        k *= c1;
&lt;/span&gt;&lt;span&gt;        k = rotl32(k, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;15&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;        k *= c2;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;        h ^= k;
&lt;/span&gt;&lt;span&gt;        h = rotl32(h, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;13&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;        h = h * &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;5 &lt;/span&gt;&lt;span&gt;+ &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xe6546b64&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    h ^= len;
&lt;/span&gt;&lt;span&gt;    h ^= h &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;16&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    h *= c3;
&lt;/span&gt;&lt;span&gt;    h ^= h &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;13&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    h *= c4;
&lt;/span&gt;&lt;span&gt;    h ^= h &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;16&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return&lt;/span&gt;&lt;span&gt; h;
&lt;/span&gt;&lt;span&gt;} 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I think by now you should be able to get this function to spit out any
value you want if you know the seed.
The inverse of
&lt;code&gt;rotl32(x, r)&lt;/code&gt; is &lt;code&gt;rotl32(x, 32-r)&lt;/code&gt; and the inverse of &lt;code&gt;h ^= h &amp;gt;&amp;gt; 16&lt;/code&gt; is
once again just &lt;code&gt;h ^= h &amp;gt;&amp;gt; 16&lt;/code&gt;. Only &lt;code&gt;h ^= h &amp;gt;&amp;gt; 13&lt;/code&gt; is a bit different, it’s the first time
we’ve seen that a xorshift’s inverse has more than one step:&lt;/p&gt;
&lt;pre style="background-color: #ffffff; color: #202020;"&gt;&lt;code&gt;&lt;span&gt;h ^= h &amp;gt;&amp;gt; 13
&lt;/span&gt;&lt;span&gt;h ^= h &amp;gt;&amp;gt; 26
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Compute the modular inverses
of &lt;code&gt;c1&lt;/code&gt; through &lt;code&gt;c4&lt;/code&gt; as well as &lt;code&gt;5&lt;/code&gt; mod $2^{32}$, and go to town.
If you want to cheat or check your answer, you can check out &lt;a href="https://gist.github.com/orlp/0c33157a0971053b60ac1da84b021bea"&gt;the code&lt;/a&gt;
I’ve used to generate the following ten strings that all hash to 1337 when
fed to &lt;code&gt;MurmurHash3_x86_32&lt;/code&gt; with seed &lt;code&gt;0&lt;/code&gt;:&lt;/p&gt;
&lt;pre style="background-color: #ffffff; color: #202020;"&gt;&lt;code&gt;&lt;span&gt;orlp-murmurhash3_x86_32-haaaPa*+
&lt;/span&gt;&lt;span&gt;orlp-murmurhash3_x86_32-saaaUW&amp;amp;&amp;lt;
&lt;/span&gt;&lt;span&gt;orlp-murmurhash3_x86_32-ubaa/!/&amp;quot;
&lt;/span&gt;&lt;span&gt;orlp-murmurhash3_x86_32-weaare]]
&lt;/span&gt;&lt;span&gt;orlp-murmurhash3_x86_32-chaa5@/}
&lt;/span&gt;&lt;span&gt;orlp-murmurhash3_x86_32-claaM[,5
&lt;/span&gt;&lt;span&gt;orlp-murmurhash3_x86_32-fraaIx`N
&lt;/span&gt;&lt;span&gt;orlp-murmurhash3_x86_32-iwaara&amp;amp;&amp;lt;
&lt;/span&gt;&lt;span&gt;orlp-murmurhash3_x86_32-zwaa]&amp;gt;zd
&lt;/span&gt;&lt;span&gt;orlp-murmurhash3_x86_32-zbbaW-5G
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nim uses &lt;code&gt;0&lt;/code&gt; as a fixed seed.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;You might wonder about the ethics of publishing functions for generating
arbitrary amounts of collisions for hash functions actually in use today. I did
consider holding back. But HashDoS has been a known attack for almost two
decades now, and the universal hash collisions I’ve shown were also published
more than a decade ago now as well. At some point you’ve had enough time
to, uh, fix your shit.&lt;/p&gt;
&lt;/aside&gt;
&lt;h3 id="universal-collision-attack-on-murmurhash3"&gt;&lt;a class="anchor" href="#universal-collision-attack-on-murmurhash3"&gt;Universal collision attack on MurmurHash3&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Suppose that Nim didn’t use &lt;code&gt;0&lt;/code&gt; as a fixed seed, but chose a randomly generated
one. Can we do a similar attack as the one done to MurmurHash2 to still generate
universal multicollisions?&lt;/p&gt;
&lt;p&gt;Yes we can. Let’s take another look at that core loop body:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;uint32_t k = loadword&amp;lt;uint32_t&amp;gt;(p);
&lt;/span&gt;&lt;span&gt;k *= c1;            &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Trivially invertable.
&lt;/span&gt;&lt;span&gt;k = rotl32(k, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;15&lt;/span&gt;&lt;span&gt;);  &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Trivially invertable.
&lt;/span&gt;&lt;span&gt;k *= c2;            &lt;/span&gt;&lt;span style="color: #76647b;"&gt;// Trivially invertable.
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;h ^= k;
&lt;/span&gt;&lt;span&gt;h = rotl32(h, &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;13&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;h = h * &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;5 &lt;/span&gt;&lt;span&gt;+ &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;0xe6546b64&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once again we can ignore the first three trivially invertable instructions as we
can simply choose our input so that we get exactly the &lt;code&gt;k&lt;/code&gt; we want.
Remember from last time that we want to introduce a difference in exactly the
top bit of &lt;code&gt;h&lt;/code&gt;, as the multiplication will leave this difference in place.
But here there is a bit rotation between the XOR  and the multiplication.
The solution? Simply place our bit difference such that &lt;code&gt;rotl32(h, 13)&lt;/code&gt; shifts
it into the top position.&lt;/p&gt;
&lt;p&gt;Does the addition of &lt;code&gt;0xe6546b64&lt;/code&gt; mess things up? No. Since only the top bit
between the two states will be different, there is a difference of exactly
$2^{31}$ between the two states. This difference is maintained by the addition.
Since two 32-bit numbers with the same top bit can be at most $2^{31} - 1$
apart, we can conclude that the two states still differ in the top bit after
the addition.&lt;/p&gt;
&lt;p&gt;So we want to find two pairs of 32-bit ints, such that after applying the first
three instructions the first pair differs in bit &lt;code&gt;1 &amp;lt;&amp;lt; (31 - 13) == 0x00040000&lt;/code&gt;
and the second pair in bit &lt;code&gt;1 &amp;lt;&amp;lt; 31 == 0x80000000&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After some brute-force searching I found some cool pairs (again forced to use
UTF-8), which when combined give the following collision:&lt;/p&gt;
&lt;pre class="language-nim " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-nim"&gt;&lt;span&gt;a = &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;!&amp;amp;orlpՓ&amp;quot;
&lt;/span&gt;&lt;span&gt;b = &lt;/span&gt;&lt;span style="color: #648424;"&gt;&amp;quot;yǏglp$X&amp;quot;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As before, any concatenation of &lt;code&gt;a&lt;/code&gt;s and &lt;code&gt;b&lt;/code&gt;s of length &lt;code&gt;n&lt;/code&gt; collides with all
other combinations of length &lt;code&gt;n&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="breaking-farmhash64"&gt;&lt;a class="anchor" href="#breaking-farmhash64"&gt;Breaking FarmHash64&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Nim switched to
&lt;a href="https://github.com/nim-lang/Nim/blob/46bb47a444bd377860d832fc1c62b262343f36a2/lib/pure/hashes.nim#L537"&gt;farmhash&lt;/a&gt;
since I started writing this post. To break it we can notice that its structure
is very similar to CityHash64, so we can use those same techniques again. In
fact, the only changes between the two for lengths 17-32 bytes is that a few
operators were changed from subtraction/XOR to addition, a rotation operator had
its constant tweaked, and some &lt;code&gt;k&lt;/code&gt; constants are slightly tweaked in usage. The
process of breaking it is so similar that it’s entirely analogous, so we can
skip straight to &lt;a href="https://gist.github.com/orlp/f0f3307530841183ddb72a0528ce0742"&gt;the result&lt;/a&gt;.
These 10 strings all hash to 1337 with FarmHash64:&lt;/p&gt;
&lt;pre style="background-color: #ffffff; color: #202020;"&gt;&lt;code&gt;&lt;span&gt;orlp-farmhash64-?VrJ@L7ytzwheaaa
&lt;/span&gt;&lt;span&gt;orlp-farmhash64-p3`!SQb}fmxheaaa
&lt;/span&gt;&lt;span&gt;orlp-farmhash64-pdt'cuI\gvxheaaa
&lt;/span&gt;&lt;span&gt;orlp-farmhash64-IbY`xAG&amp;amp;ibkieaaa
&lt;/span&gt;&lt;span&gt;orlp-farmhash64-[_LU!d1hwmkieaaa
&lt;/span&gt;&lt;span&gt;orlp-farmhash64-QiY!clz]bttieaaa
&lt;/span&gt;&lt;span&gt;orlp-farmhash64-&amp;amp;?J3rZ_8gsuieaaa
&lt;/span&gt;&lt;span&gt;orlp-farmhash64-LOBWtm5Szyuieaaa
&lt;/span&gt;&lt;span&gt;orlp-farmhash64-Mptaa^g^ytvieaaa
&lt;/span&gt;&lt;span&gt;orlp-farmhash64-B?&amp;amp;l::hxqmfjeaaa
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="trivial-fixed-seed-wyhash-multicollisions"&gt;&lt;a class="anchor" href="#trivial-fixed-seed-wyhash-multicollisions"&gt;Trivial fixed-seed wyhash multicollisions&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Zig uses &lt;a href="https://github.com/wangyi-fudan/wyhash/blob/master/wyhash.h"&gt;wyhash&lt;/a&gt;
with a fixed seed of zero. While I was unable to do
seed-independent attacks against wyhash, using it with a fixed seed makes
generating collisions trivial. Wyhash is &lt;a href="https://github.com/wangyi-fudan/wyhash/blob/46cebe9dc4e51f94d0dca287733bc5a94f76a10d/wyhash.h#L46"&gt;built upon&lt;/a&gt;
the folded multiply, which takes two 64-bit inputs, multiplies them to a 128-bit product before XORing
together the two halves:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;uint64_t folded_multiply(uint64_t a, uint64_t b) {
&lt;/span&gt;&lt;span&gt;    __uint128_t full = __uint128_t(a) * __uint128_t(b);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="font-weight: bold; color: #202020;"&gt;return &lt;/span&gt;&lt;span&gt;uint64_t(full) ^ uint64_t(full &amp;gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #ae6018;"&gt;64&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;aside&gt;
For the record, this is not a knock against wyhash in general. It seems solid
if used with a secret randomized seed. My goal is only to illustrate that if
used with a fixed seed it trivially breaks down.
&lt;/aside&gt;
&lt;p&gt;It’s easy to immediately see a critical flaw with this: if one of the two sides
is zero, the output will also always be zero. To protect against this, wyhash
always uses a folded multiply in the following form:&lt;/p&gt;
&lt;pre class="language-cpp " style="background-color: #ffffff; color: #202020;"&gt;&lt;code class="language-cpp"&gt;&lt;span&gt;out = folded_multiply(input_a ^ secret_a, input_b ^ secret_b);
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;where &lt;code&gt;secret_a&lt;/code&gt; and &lt;code&gt;secret_b&lt;/code&gt; are determined by the seed, or outputs of
previous iterations which are influenced by the seed. However, when your seed is
constant… With &lt;a href="https://gist.github.com/orlp/a9cc8dae3a74b1faaa0a642135ee81df"&gt;a bit of creativity&lt;/a&gt;
we can use the start of our string to prepare a ‘secret’ value which we can
perfectly cancel with another ASCII string later in the input.&lt;/p&gt;
&lt;p&gt;So, without further ado, every 32-byte string of the form&lt;/p&gt;
&lt;pre style="background-color: #ffffff; color: #202020;"&gt;&lt;code&gt;&lt;span&gt;orlp-wyhash-oGf_________tWJbzMJR
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;hashes to the same value with Zig’s default hasher.&lt;/p&gt;
&lt;p&gt;Zig uses a different set of parameters than the defaults found in the wyhash
repository, so for good measure, this pattern provides arbitrary multicollisions
for the default parameters found in wyhash when using &lt;code&gt;seed == 0&lt;/code&gt;:&lt;/p&gt;
&lt;pre style="background-color: #ffffff; color: #202020;"&gt;&lt;code&gt;&lt;span&gt;orlp-wyhash-EUv_________NLXyytkp
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id="conclusion"&gt;&lt;a class="anchor" href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We’ve seen that a lot of the hash functions in common use in hash tables today
are very weak, allowing fairly trivial attacks to produce arbitrary amounts of
collisions if not randomly initialized. Using a randomly seeded hash table is
paramount if you don’t wish to become a victim of a hash flooding attack.&lt;/p&gt;
&lt;p&gt;We’ve also seen that some hash functions are vulnerable to attack &lt;em&gt;even if
randomly seeded&lt;/em&gt;. These are completely broken and should not be used if attacks
are a concern at all. Luckily I was unable to find such attacks against most
hashes, but the possibility of such an attack existing is quite unnerving.&lt;/p&gt;
&lt;p&gt;With &lt;a href="https://en.wikipedia.org/wiki/Universal_hashing"&gt;universal hashing&lt;/a&gt; it’s
possible to construct hash functions for which such an attack is provably
impossible, last year I published a hash function called
&lt;a href="https://github.com/orlp/polymur-hash"&gt;polymur-hash&lt;/a&gt; that has this property. Your
HTTPS connection to this website also likely uses a universal hash function
for authenticity of the transferred data, both &lt;a href="https://en.wikipedia.org/wiki/Poly1305"&gt;Poly1305&lt;/a&gt;
and &lt;a href="https://en.wikipedia.org/wiki/Galois/Counter_Mode"&gt;GCM&lt;/a&gt; are based on
universal hashing for their security proofs.&lt;/p&gt;
&lt;aside&gt;
&lt;p&gt;Well, such attacks are provably impossible against non-interactive attackers, everything goes out of the
window again when an attacker is allowed to inspect the output hashes and use
that to try and guess your secret key.&lt;/p&gt;
&lt;/aside&gt;
&lt;p&gt;Of course, if your data is not user-controlled, or there is no reasonable
security model where your application would face attacks, you can get away with
faster and insecure hashes.&lt;/p&gt;
&lt;p&gt;More to come on the subject of hashing and hash
tables and how it can go right or wrong, but for now this article is long enough as-is…&lt;/p&gt;</description><author>orlp.net - Blog Archive</author><pubDate>Sat, 02 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://orlp.net/blog/breaking-hash-functions/</guid></item><item><title>The evolution of incident response at Podia</title><link>https://jamie.ideasasylum.com/2024/11/02/the-evolution-of-incident-response-at-podia</link><description>&lt;p&gt;Over the years, Podia has gone from having no incident response processes to a more structured system of on-call shifts and supportive tooling. You might be thinking of embarking on the same journey, or be confused why any developer would embrace the responsibility of on-call shifts, or wonder how you can introduce it without pissing off your entire team.&lt;/p&gt;

&lt;p&gt;Here’s how I did it.&lt;/p&gt;

&lt;h2 id="life-without-on-call"&gt;Life without on-call&lt;/h2&gt;

&lt;p&gt;It’s blissful, right? No late-night phone calls. No need to remember your laptop or modify your plans. Nothing ever goes wrong. The app never goes down, your providers never fail, deploys never break anything, and customers are never impacted.&lt;/p&gt;

&lt;p&gt;I mean, that’s what you &lt;em&gt;think&lt;/em&gt; happens but ignorance doesn’t make it true. And this is not the reality we lived under.&lt;/p&gt;

&lt;p&gt;In the earliest days, we had no monitoring at all which is where all projects start out. Pretty soon though I added Rollbar to start tracking exceptions that happened in production and pipe those notifications into Slack. And then we noticed the app might be briefly unresponsive at times so we added a simple uptime checker (UptimeRobot) to tell us if the app stopped responding.&lt;/p&gt;

&lt;p&gt;Back in the early days when Podia had just one, two, or three or four full-time developers we didn’t have any defined processes for handling these notifications. Instead, our typical daily routine for each of us went something like this: wake up, check Slack for errors (because of cause of it was on our phones), have breakfast, check Slack for errors, do some work whilst checking Slack for errors, run some errands whilst checking Slack for errors…&lt;/p&gt;

&lt;p&gt;Having every developer poll Slack for news about problems is not very efficient and over the long-term it led to a lot of internalised stress and apprehension. When we were constantly logging into Slack, especially during those moments of personal time when we’re supposed to be resting, it quickly developed into a neurotic pattern of behaviour.&lt;/p&gt;

&lt;p&gt;I remember being in town one Saturday with the family and a checked Slack out of habit as I was walking down the street. There I saw a ton of errors for, I think, our background jobs being killed due to OutOfMemory errors. Since I was about an hour from home, without a laptop, and supposed to be enjoying an afternoon with the family, there wasn’t much I could do about the problem. Luckily, Jason had woken up in Tennessee, saw the messages and was able to resolve the issue.&lt;/p&gt;

&lt;p&gt;We were lucky because we didn’t have many users at the time and they weren’t actively using the platform at all times of the day and week. But that was changing fast…&lt;/p&gt;

&lt;h2 id="introducing-on-call"&gt;Introducing on-call&lt;/h2&gt;

&lt;p&gt;I knew we needed an on-call system but I was also extremely wary of it becoming an exploitative system that the developers would come to resent.&lt;/p&gt;

&lt;p&gt;In the 20 years of my career, I’d never had to be on-call for a system. I mostly worked in research or enterprise apps, and briefly in a Level 3 Support role, but if you’d asked me I’d have said that being on-call was for the IT staff. I also, frankly, would never have cared enough about those jobs or trusted any of the companies I worked for to manage the on-call shifts in a responsible manner. The web has changed all that. In particular, the global reach of our apps means we have a never-off cycle of traffic that cannot be confined to any normal business hours. Things &lt;em&gt;will&lt;/em&gt; go wrong when we are asleep and they &lt;em&gt;will&lt;/em&gt; impact our users.&lt;/p&gt;

&lt;p&gt;I used these principles when designing our on-call system, though most flow from the first:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;Minimise the impact on our personal lives&lt;/li&gt;
  &lt;li&gt;Being on-call should be as low-stress as possible&lt;/li&gt;
  &lt;li&gt;Restrict the number of things that can trigger an incident&lt;/li&gt;
  &lt;li&gt;Have &lt;em&gt;reasonable&lt;/em&gt; expectations on response times etc&lt;/li&gt;
  &lt;li&gt;The whole team has responsibility for maintaining the app so we will work as a team on-call&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id="how-often"&gt;How often?&lt;/h3&gt;

&lt;p&gt;First, I needed to decide on the shift structure for the team. The most popular rotation schedules are either weekly (on-call for 7 days), daily (on-call for 24 hours), or something more sophisticated like follow-the-sun or business-hours/out-of-hours cover. For me, the only reasonable choice was between weekly or daily.&lt;/p&gt;

&lt;p&gt;Weekly schedules have the advantage that they don’t come around too often. With just four of us, you’d only be on-call for 1 week in every 4. The downside though is that you’re going to be carrying your laptop around for a whole week and that’s very disruptive. Selfishly, it would have meant that I couldn’t swim for an entire week and that was a major disadvantage to me.&lt;/p&gt;

&lt;p&gt;In my opinion, 24-hour shifts are the ideal frequency so that’s what we went with and it hasn’t changed since. Your life is only mildly inconvenienced for a day, so it’s relatively easy to schedule things around being on-call. It’s also easier to find someone to cover a shift for you, doesn’t interfere with a whole weekend, and if we hit a patch of poor reliability those incidents are more likely to be distributed across the team. The downside is that you’ll be on-call more frequently but as the team size increases so the on-call frequency decreases.&lt;/p&gt;

&lt;h3 id="what-wakes-us-up"&gt;What wakes us up?&lt;/h3&gt;

&lt;p&gt;As a kid I remember sleeping out one night in a tent in our back garden—long before mobile phones and clearly before my parents would trust us with a house key. But my dad had thought of an ingenious solution in case we needed to come into the house in the middle of the night: he tied a piece of string around his toe and we were to tug on it we needed to be let back in. What things could wake us up when we were on-call? What alerts would be tied to our toes with a piece of string to wake us up in the middle of the night? That’s a lot of trust to place is something.&lt;/p&gt;

&lt;p&gt;I knew there would be pressure to expand the on-call system over time, especially as users encountered bugs or wrote in with irate support requests because we didn’t respond to their particular issue overnight. We had to be ready to resist the scope-creep of being on-call in order to make it sustainable so I felt it was important to place limits on this process.&lt;/p&gt;

&lt;p&gt;I settled on having just three events being worthy of waking us up and, if we needed to add another alert, then one of these would have to go:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;the app is down, as detected by some external monitor&lt;/li&gt;
  &lt;li&gt;our background job latency is over a defined period, meaning there are too many jobs piling up in the queue&lt;/li&gt;
  &lt;li&gt;our custom domain proxy was not working&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Today, the first two events are still the main alerts that we use. Our custom domain proxy has been replaced by Cloudflare so it’s no longer a source of downtime, and is no longer monitored separately. Instead, our third alert source is the facility for anyone in our Slack—typically our support team—to declare an incident and page the on-call developer. It’s rarely used and I think it’s taken time to build that trust. I’m not sure we’d have introduced it on day 1.&lt;/p&gt;

&lt;p&gt;It’s also important to have some expectations around how often we expect incidents. For us, it’s once or twice &lt;em&gt;a month&lt;/em&gt;. They should not be daily or weekly occurrences.&lt;/p&gt;

&lt;p&gt;I’m pretty satisfied with these criteria and if you were starting out today I would still recommend you only page the on-call developer if a) the app is down or b) the background jobs aren’t processing at a rate you expect. Everything else is noise and anything significant will usually trigger one of these alerts.&lt;/p&gt;

&lt;p&gt;I am pretty definite that &lt;em&gt;exceptions are not incidents&lt;/em&gt;. An exception can be generated by a malformed client, or a user encountering a transient issue just once, or a background job hitting a timeout and then retrying successfully. Or a validation triggered an error but the user completed the form anyway. Hell, even if they couldn’t complete the form, it’s still something you can solve &lt;em&gt;tomorrow&lt;/em&gt;. Unless you are in an industry with severe consequences for errors, none of these things rise to the level that should wake your team up.&lt;/p&gt;

&lt;h3 id="expectations"&gt;Expectations&lt;/h3&gt;

&lt;p&gt;I had read accounts of on-call engineers at Google being required to always be within arms-reach of their laptops and that didn’t sound like a life I wanted to live—or provide for the rest of the team. Especially as many of us had families and hobbies and our app being down for a few minutes was not going to kill anyone.&lt;/p&gt;

&lt;p&gt;Our expectations are, in my opinion, much more reasonable: respond within 10-15mins. That means you can leave the laptop in the car when you go shopping; or go for a short walk near your house. We just expect that the developer will be ready to triage the incident within this reasonable timeframe&lt;/p&gt;

&lt;p&gt;I even addressed whether you can drink when on-call: sure, just don’t operate production systems when drunk! We all might enjoy a beer or glass of wine after work and I didn’t want to restrict our team’s social lives, and neither did I want anyone to hide the fact they’d been out on a heavy session when on-call. If you’re drunk, please don’t acknowledge the incident!&lt;/p&gt;

&lt;h3 id="teamwork"&gt;Teamwork&lt;/h3&gt;

&lt;p&gt;I’ve always been very clear that the app is the whole team’s responsibility. It’s not just mine. And you can’t just claim that little corner over there as yours. We all have a collective responsibility to keep this thing alive. “It’s not my job” is not an attitude that’s tolerated at Podia: if you ship code at Podia then it’s your job to look after it.&lt;/p&gt;

&lt;p&gt;And that means when you ship some dodgy code, it’s going to wake you, or one of your colleagues up at night. Or when you review a PR and don’t speak up about your concerns, that’s the thing which could wake you up. &lt;a href="https://jamie.ideasasylum.com/2024/10/30/moving-off-heroku-slowly"&gt;When we advocate for infrastructure choices&lt;/a&gt;, we do so knowing that all of us will be on-call to support it.&lt;/p&gt;

&lt;p&gt;Incidents are best played as a team sport since, ideally, most of them should ideally be novel situations that we haven’t encountered before.&lt;/p&gt;

&lt;p&gt;When a developer acknowledges an incident, they will start to triage it to understand what happened and why, what the resolution might be, etc. It’s rarely something that you can look up in a playbook and run through a sequence of commands to resolve. We used to have a “runbook” but most of those situations have now been engineered away so we don’t really encounter them now.&lt;/p&gt;

&lt;p&gt;There’s nothing more frightening than receiving a 4am call, blurry-eyed, scrambling for your laptop, and then &lt;em&gt;all on your own&lt;/em&gt; trying to decide how best to handle the backlog of jobs in the queue. Do you pause the process? Delete the enqueued jobs? Increase the concurrency? Yikes, no. You need some help.&lt;/p&gt;

&lt;p&gt;It’s the general expectation that if you see an incident in-progress, you join it and offer to help. You never let your colleague handle the incident on their own as you continue to work on whatever you were doing—because &lt;em&gt;nothing&lt;/em&gt; you could be working on is more important than helping out a teammate.&lt;/p&gt;

&lt;p&gt;Since Day 1 we’ve announced all incidents in Slack &lt;em&gt;immediately&lt;/em&gt; as soon as the alert is triggered, and then paged the on-call developer a minute or two later. This means that if the incident happens at 4am in New York, an EU developer working at 9am can spot the incident notification and acknowledge it, thereby saving their colleague from a very early morning wake-up call. It’s not only the on-call developer that can acknowledge the incidents.&lt;/p&gt;

&lt;p&gt;This broadly works because Podia developers are spread across European and North American timezones, so we have pretty good coverage for 12-16 hours of the day during the week. But in cases when the developer is still left dealing with the incident on their own, they can page me (I’m CTO so I’m always on-call even when I’m not) or another developer on the team to help. We now even have a prompt in the incident channel after a short delay asking “Do you have everyone you need to resolve this incident?” and instructions for how to page someone else.&lt;/p&gt;

&lt;p&gt;I don’t like incidents; they’re stressful for the team and each one impacts our users. But they are also a special bonding moment for us as a team. There’s a great camaraderie that comes from working together under a (hopefully!) brief stressful situation, solving a puzzle together, and remediating the problem. A little bit of adrenaline is good for the soul! There’s also a counter-intuitive argument that you can’t get good at something that only happens occasionally. We need incidents (😅) so that we can collectively learn how our systems function and how to respond to them when they fail.&lt;/p&gt;

&lt;p&gt;Being on-call also has other ways we can demonstrate teamwork and strengthen relationships, particularly important on a remote team. Shift swapping is common and encouraged; being flexible is another way of limiting the impact to each of our lives, and another opportunity to say “I got you”. When someone joins the team, they’re not on-call for the first month. If people are up handling an incident overnight then I’ll take over the rest of their shift and tell them to take the morning off. When someone has a baby, we take them off the on-call rota for a few months. When someone leaves the team, I typically take over that person’s shifts for a while so the team doesn’t have to absorb the impact straight away. All small gestures of teamwork.&lt;/p&gt;

&lt;p&gt;All in all, having an on-call rota is one of the few ways we can work together as a whole team rather than working on projects in pairs.&lt;/p&gt;

&lt;h2 id="on-call-over-time"&gt;On-call, over time&lt;/h2&gt;

&lt;p&gt;Our on-call process was introduced a good few years ago now and it has largely remained unchanged. It’s still a 24-hour rota, only three things can wake us up, and we treat it as a whole-team responsibility. Some things have changed though.&lt;/p&gt;

&lt;h3 id="team-size"&gt;Team size&lt;/h3&gt;

&lt;p&gt;When we started out, I think there was only 4, maybe 5, of us on the rota. At our largest, there was been 12 people on team, which led to a very relaxed on-call frequency. If anything, that was verging on being too infrequent and had the team scaled to &amp;gt;15 developers, I think I would have introduced a two-layer on-call system so there would always be a primary and secondary developer on-call.&lt;/p&gt;

&lt;p&gt;At the moment, we’re just 8 people on the dev team which narrowly avoids the awkward situation with 7 people where the same person would always be on-call for the same day of the week.&lt;/p&gt;

&lt;p&gt;I do think it’s worth having some on-call system even with just a single developer to avoid obsessively checking slack but, generally, the more people in the rota the better it is for everyone. Less than 4 developers is always going to be a tough situation.&lt;/p&gt;

&lt;p&gt;We also now have the entire leadership team pageable (but not on a rota) so for larger incidents we can pull in Support or Marketing help to communicate with our users about the incident.&lt;/p&gt;

&lt;h3 id="tools"&gt;Tools&lt;/h3&gt;

&lt;p&gt;Our tool choice has also changed over the years. We started out using OpsGenie because the UI didn’t make my eyes bleed like PagerDuty did. Not that it was great but it was mostly useable.&lt;/p&gt;

&lt;p&gt;We also used UptimeRobot to monitor the app’s uptime across a few different endpoints and assess the background queue latency by polling an endpoint that reported the metric.&lt;/p&gt;

&lt;p&gt;Our stack today starts with &lt;a href="https://cronitor.io"&gt;Cronitor&lt;/a&gt; which has taken over app monitoring duties from UptimeRobot. When Cronitor detects a problem, it triggers an incident in incident.io. This provides us with a Slack app for managing the incident process: it auto-creates a slack channel, broadcasts the incident notification to a company-wide &lt;code class="highlighter-rouge"&gt;#podia-updates&lt;/code&gt; channel, pages the on-call developer, and triggers some timed-prompts during the incident (like “Do you have everyone you need to resolve this incident?”). We can also assign tasks inside the incident Slack channel, pin messages for an after-action incident report, and update our internal incident status. It also makes it incredibly easy to escalate the incident to another developer, knowing that it will break through their do-not-disturb settings if required. If necessary, anyone in the company can create an incident from within Slack using incident.io.&lt;/p&gt;

&lt;p&gt;Our status page is also hosted by incident.io so anyone at the company can update it from within Slack. This is great during an incident when the devs might be heads-down trying to figure out the problem and someone from support can craft a more detailed message for our users.&lt;/p&gt;

&lt;p&gt;Once the incident is resolved, we can write up an incident report using a timeline compiled automatically by incident.io from the posts in the Slack channel, including relevant screenshots, code snippets, PR links etc. We’re not particularly rigorous with this process but it still represents a valuable body of work, especially for new developers joining the team and wondering what they’re getting themselves into.&lt;/p&gt;

&lt;p&gt;We also rely heavily on &lt;a href="https://www.dataset.com"&gt;Dataset&lt;/a&gt; (a.k.a. Scalyr before they were acquired) to give us an at-a-glance dashboard and the ability to filter, search, and chart data from our logs. We use AppSignal for exception tracking and more fine-grained performance profiling.&lt;/p&gt;

&lt;p&gt;But the tools don’t really matter. If I was doing this today from scratch, I’d probably look at &lt;a href="https://betterstack.com"&gt;BetterStack&lt;/a&gt; because they literally have everything you need under one roof.&lt;/p&gt;

&lt;h2 id="takeaways"&gt;Takeaways&lt;/h2&gt;

&lt;p&gt;What matters first is thinking about how an on-call rota can improve the lives of your team and business—being on-call &lt;em&gt;sometimes&lt;/em&gt; is infinitely better than everyone &lt;em&gt;constantly&lt;/em&gt; checking Slack on their time off and living with the permanent stress that the app might be down.&lt;/p&gt;

&lt;p&gt;Once you’ve accepted the necessity and desire for having an on-call rota, you can design the process in such a way that it minimises the stress for those involved and promotes stronger tied within the team. You can place limits on it and make it flexible enough so that the impact is quite minimal.&lt;/p&gt;

&lt;p&gt;These days, being on-call for Podia means I throw my laptop and AirPods in a bag when I leave the house. Or I choose to do jobs around the house. I can’t swim for that 24 hour period but that’s ok: I can still go to the gym, or run errands, go out for a meal, or watch a movie at the cinema.&lt;/p&gt;

&lt;p&gt;I can do that because the demands are low, I know the team will have my back if they’re working anyway, and incidents are pretty rare. Typically we have ~1 incident per month and these rarely involve waking someone up: usually it’s caught by a developer who was working in their timezone or because it occurred during normal working hours (because—&lt;em&gt;sssshhhhhh&lt;/em&gt;—the main cause of downtime is actually developers doing developer-things 😅. Who knew?!). At times, we have had spurious alerts that triggered an incident before the alert was auto-resolved. These were very frustrating for everyone but we try to investigate them and put in preventative measures to squash these alerts.&lt;/p&gt;

&lt;p&gt;No system is perfect but I do believe you can structure the process to make it less burdensome.&lt;/p&gt;</description><author>Jamie’s blog</author><pubDate>Sat, 02 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://jamie.ideasasylum.com/2024/11/02/the-evolution-of-incident-response-at-podia</guid></item><item><title>GeoClue TZ: Privacy-First Linux Location Service</title><link>https://thoughts.greyh.at/posts/geoclue-tz/</link><description>Linux location services provided by GeoClue have been historically unreliable. Whether it&amp;rsquo;s VPN usage causing incorrect locations, rate limits from upstream services, or broken functionality after system upgrades, users often find themselves struggling with basic location-aware features.</description><author>Terminal Thoughts</author><pubDate>Sat, 02 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://thoughts.greyh.at/posts/geoclue-tz/</guid></item><item><title>Note 152</title><link>https://qubyte.codes/notes/1730457486729</link><description>&lt;p&gt;Spotted my first jay of the autumn! I think this is probably as close as I’ve ever been to one.&lt;/p&gt;

    &lt;img alt="A jay standing, its left side facing the camera, on some grass. Lots of leaf litter too." src="https://qubyte.codes/images/1730457375138.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Fri, 01 Nov 2024 12:38:06 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1730457486729</guid></item><item><title>Release: OuroborosDB Data Storage Calculator</title><link>https://heidenstedt.org/posts/2024/release-ouroborosdb-data-storage-calculator/</link><description>&lt;p&gt;
      &lt;em&gt;Best viewed on the &lt;a href="https://heidenstedt.org/posts/2024/release-ouroborosdb-data-storage-calculator/"&gt;original page&lt;/a&gt;, where extended functionality like the
    footnote helper is available.&lt;/em&gt;
    &lt;/p&gt;&lt;p&gt;I forgot to mention that i released a new tool for my project &lt;a href="https://github.com/i5heu/ouroboros-db"&gt;ouroboros-db&lt;/a&gt;, so here it is:&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;OuroborosDB Overhead Calculator&amp;rdquo; is a little tool which helps you to calculate the overhead of erasure coding systems while also considering the overhead of indexes and the blocks themselves. It is kinda fun to play around with so give it a try!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://i5heu.github.io/ouroboros-db-overhead-calculator/"&gt;https://i5heu.github.io/ouroboros-db-overhead-calculator/&lt;/a&gt;&lt;/p&gt;
&lt;video controls="controls" width="100%"&gt;
  &lt;source src="https://heidenstedt.org/posts/2024/release-ouroborosdb-data-storage-calculator/demo.webm" type="video/webm" /&gt;
  Your browser does not support the video tag.
&lt;/video&gt;</description><author>Mia Heidenstedt</author><pubDate>Fri, 01 Nov 2024 11:17:10 GMT</pubDate><guid isPermaLink="true">https://heidenstedt.org/posts/2024/release-ouroborosdb-data-storage-calculator/</guid></item><item><title>Moving in Big Cities: Lugg vs Clutter</title><link>https://www.swyx.io/lugg-vs-clutter</link><description>&lt;blockquote&gt;
&lt;p&gt;2026 update: used Lugg to move a sectional sofabed couch and ended up regretting it - it cost $222.51 one way (44min labor pickup 55min labor dropoff, which i think was fraud, no way dropoff cost more than pickup) and total base fare 38 + 14.5 fee. for the two way move price i could have simply ordered a new couch and had a nice new couch.&lt;/p&gt;
&lt;/blockquote&gt;</description><author>swyx's site RSS Feed</author><pubDate>Fri, 01 Nov 2024 11:12:31 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/lugg-vs-clutter</guid></item><item><title>Getting Started with Candlesticks Patterns and Python</title><link>https://blog.adnansiddiqi.me/getting-started-with-candlesticks-patterns-and-python/</link><description>&lt;p&gt;This post is part of the T4p Series. In the previous post, I briefly introduced candlesticks and how to detect a bearish and bullish candle in Python. In this post, we are going deeper, discussing candlestick patterns, what they are all about, and how to detect and represent them in Python Introduction As you learned in the previous post, there are mainly two types of candles: Bearish and Bullish. The size of the wick and the body determines what kind of bearish and bullish candle it is. Candlestick patterns visually help traders learn about the price movement to interpret the market sentiments. History Candlestick patterns were developed centuries ago by Japanese rice traders looking to predict price shifts in the market. Today, traders worldwide use these same patterns to spot potential trends and reversals, blending ancient wisdom with modern trading techniques. Types of Candlestick Patterns In a broader sense, we can divide patterns into two categories: Single Candlestick Patterns: Doji, Hammer, and Shooting Star. Multiple Candlestick Patterns: Engulfing, Morning Star, and Evening Star. Some of the patterns represent either a bearish or bullish market while other patterns emerge in both. There are many candlestick patterns, such as Doji, Engulfing, Hammer, Morning Star, Evening Star, etc. I can&amp;#8217;t cover all those in a single post, so I will try to cover them in separate posts. For now, I am discussing the Doji and Engulfing patterns here. Doji Pattern It is a pattern that occurs when the opening and closing prices are virtually equal, creating a cross or a plus sign shape. The name Doji comes from Japanese and means blunder or mistake, referring to the rarity of having the same opening and closing prices. Usage Doji is used as a reversal pattern. After an Uptrend: When you spot a Doji after a strong upward move Wait for confirmation (next candle closing lower) Consider taking profit on long positions or entering short positions Place stop loss above the Doji&amp;#8217;s high After a Downtrend: When you spot a Doji after a strong downward move Wait for confirmation (next candle closing higher) Consider entering long positions or covering shorts Place stop loss below the Doji&amp;#8217;s low Types of Doji Standard Doji: Open and Close should be the same/very close Equal shadows above and below Dragonfly Doji: Open and Close at/near the high Long lower shadow Little to no upper shadow Gravestone Doji: Open and Close at/near the low Long upper shadow Little to no lower shadow Visual Representation Now let&amp;#8217;s write some code in Python to learn how you can represent Doji patterns In [27]: import mplfinance as mpf import pandas as pd In [28]: def plot_candlestick_from_dict(data,title="CandleStick Chart"): df = pd.DataFrame(data) df['Date'] = pd.to_datetime(df['Date']) df.set_index('Date', inplace=True) mpf.plot(df, type='candle', style='charles', title=title, ylabel='Price') Standard Doji In [29]: ohlc_data = [ {"Date":"2024-09-01", "Open":100, "High":105, "Low":95, "Close":100}, ] plot_candlestick_from_dict(ohlc_data,"Standard Doji") Dragonfly Doji In [30]: ohlc_data = [ {"Date":"2024-09-01", "Open":100, "High":100.5, "Low":95, "Close":100}, ] plot_candlestick_from_dict(ohlc_data,"Dragonfly Doji") Gravestone Doji In [31]: ohlc_data = [ {"Date":"2024-09-01", "Open":100, "High":105, "Low":99.5, "Close":100}, ] plot_candlestick_from_dict(ohlc_data,"Gravestone Doji") I have embedded the entire notebook. Visual representation is not enough. Now let&amp;#8217;s write some code to detect Doji patterns in the trading OHLC data. def is_doji(open_price, high, low, close, doji_size=0.1): """ Check if a candlestick is a Doji pattern Parameters: open_price: Opening price high: High price low: Low price close: Closing price doji_size: maximum body size ratio threshold (default 0.1 or 10% of range) """ body_size = abs(close - open_price) candle_range = high - low # Avoid division by zero if candle_range == 0: return False # Calculate body size as a ratio of the candle range body_ratio = body_size / candle_range return body_ratio &amp;#60;= doji_size I have written functions for all Doji types but here I am explaining the core concept that will be helpful to figure out different Doji types. Here are a few concepts: Body Size: It should be very small for Doji. We are computing it by taking the difference between closing and opening prices. Candle Range: Total range of price movement. Body Ratio: Compares the body to the total range. Doji Size: Threshold (default 0.1 or 10%). The visual representation would be something like the below: Normal Candle: Doji Candle: High 105 ─┬─ High 105 ─┬─ │ │ │ │ Open 100 ─┤ Open 100 ─┼─ Close 100.2 │ │ │ │ Close 95 ─┴─ Low 95 ─┴─ Body Ratio: 30% Body Ratio: 2% (Not a Doji) (Is a Doji!) You can see the entire code in the Jupyter Notebook. Engulfing Pattern It is formed by two candles where the body of the first candle is engulfed by the body of the second candle. Trading volume often increases on the engulfing candle. There are two types of engulfing patterns: Bearish and Bullish. Bullish Engulfing: A two-candle pattern where a smaller bearish candle is completely &amp;#8220;engulfed&amp;#8221; by a larger bullish candle, suggesting a potential upward reversal. Bearish Engulfing: The opposite, where a smaller bullish candle is engulfed by a larger bearish candle, hinting at a possible downward trend. Visual Representation Below is the exported Jupyter Notebook for the visual representation of the engulfing pattern in Python: &amp;#160; &amp;#160; In [65]: import mplfinance as mpf import pandas as pd In [66]: def plot_engulfing_pattern(data, title): # Convert to DataFrame df = pd.DataFrame(data) df['Date'] = pd.to_datetime(df['Date']) df.set_index('Date', inplace=True) # Create the plot mpf.plot(df, type='candle', title=title, volume=False, # Set to True if you have volume data style='charles', figsize=(10,5)) In [67]: bullish_engulfing_data = [ # Downtrend before pattern {"Date":"2024-09-01", "Open":110, "High":112, "Low":108, "Close":109}, {"Date":"2024-09-02", "Open":109, "High":110, "Low":106, "Close":107}, {"Date":"2024-09-03", "Open":107, "High":108, "Low":104, "Close":105}, # Engulfing pattern {"Date":"2024-09-04", "Open":105, "High":105.5, "Low":103, "Close":104}, # Small bearish candle {"Date":"2024-09-05", "Open":103, "High":108, "Low":102, "Close":107}, # Bullish engulfing # Uptrend after pattern {"Date":"2024-09-06", "Open":107, "High":110, "Low":106, "Close":109} ] # Sample data showing Bearish Engulfing Pattern bearish_engulfing_data = [ # Uptrend before pattern {"Date":"2024-09-01", "Open":100, "High":102, "Low":99, "Close":101}, {"Date":"2024-09-02", "Open":101, "High":104, "Low":100, "Close":103}, {"Date":"2024-09-03", "Open":103, "High":106, "Low":102, "Close":105}, # Engulfing pattern {"Date":"2024-09-04", "Open":105, "High":108, "Low":104, "Close":107}, # Small bullish candle {"Date":"2024-09-05", "Open":108, "High":109, "Low":103, "Close":104}, # Bearish engulfing # Downtrend after pattern {"Date":"2024-09-06", "Open":104, "High":105, "Low":101, "Close":102} ] In [68]: plot_engulfing_pattern(bullish_engulfing_data, 'Bullish Engulfing Pattern') plot_engulfing_pattern(bearish_engulfing_data, 'Bearish Engulfing Pattern') &amp;#160; I added three functions to detect both bearish and bullish candles: def is_bearish_engulfing(current_candle, previous_candle): """ Detect bearish engulfing pattern. Args: current_candle (dict): Current day's OHLC data previous_candle (dict): Previous day's OHLC data """ # Check if previous candle is bullish prev_bullish = previous_candle["Close"] &amp;#62; previous_candle["Open"] # Check if current candle is bearish curr_bearish = current_candle["Close"] &amp;#60; current_candle["Open"] # Check engulfing conditions engulfs_body = (current_candle["Open"] &amp;#62; previous_candle["Close"] and current_candle["Close"] &amp;#60; previous_candle["Open"]) return prev_bullish and curr_bearish and engulfs_body def is_bullish_engulfing(current_candle, previous_candle): """ Detect bullish engulfing pattern. Args: current_candle (dict): Current day's OHLC data previous_candle (dict): Previous day's OHLC data """ # Check if previous candle is bearish prev_bearish = previous_candle["Close"] &amp;#60; previous_candle["Open"] # Check if current candle is bullish curr_bullish = current_candle["Close"] &amp;#62; current_candle["Open"] # Check engulfing conditions engulfs_body = (current_candle["Open"] &amp;#60; previous_candle["Close"] and current_candle["Close"] &amp;#62; previous_candle["Open"]) return prev_bearish and curr_bullish and engulfs_body def find_engulfing_patterns(candles): """ Find all engulfing patterns in a list of candles. Args: candles (list): List of dictionaries containing OHLC data """ patterns = [] for i in range(1, len(candles)): current = candles[i] previous = candles[i-1] if is_bearish_engulfing(current, previous): patterns.append({ "date": current["Date"], "pattern": "Bearish Engulfing", "previous_candle": previous, "engulfing_candle": current }) if is_bullish_engulfing(current, previous): patterns.append({ "date": current["Date"], "pattern": "Bullish Engulfing", "previous_candle": previous, "engulfing_candle": current }) return patterns When you run these functions you&amp;#8217;ll see something like the below in the notebook: Conclusion There are many other patterns that I cannot cover in this post. It has already become quite lengthy. In the coming posts, I will be discussing each pattern in a separate post. Like always, the code is available on GitHub. Have a winning trading strategy and want it to trade for you automatically, without lifting a finger? I specialize in turning your strategies into fully automated trading bots—whether for stocks, crypto, or forex. Let your strategy work around the clock and maximize your potential. Ready to bring it to life? Email me at kadnan @ gmail.com If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/getting-started-with-candlesticks-patterns-and-python/"&gt;Getting Started with Candlesticks Patterns and Python&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Fri, 01 Nov 2024 09:18:52 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/getting-started-with-candlesticks-patterns-and-python/</guid></item><item><title>Writing GDScript with Neovim</title><link>https://www.alicegg.tech//2024/11/01/godot-nvim.html</link><description>&lt;p&gt;Neovim is by far my favorite text editor.
The clutter-free interface and keyboard-only navigation are what keep me productive in my daily programming.
In an &lt;a href="https://alicegg.tech/2022/04/20/vim-plugins"&gt;earlier post&lt;/a&gt;, I explained how I configure it into a minimalist development environment.
Today, I will show you how to use it with Godot and GDScript.&lt;/p&gt;

&lt;figure&gt;
	&lt;img alt="A dramatic vim screenshot" src="/assets/2024-11-01-godot-nvim/vimshot.jpg" width="100%" /&gt;
&lt;/figure&gt;

&lt;h1 id="configure-godot"&gt;Configure Godot&lt;/h1&gt;

&lt;p&gt;First, we need to tell Godot to use nvim as a text editor instead of the built-in one.
Open Godot, and head to &lt;code class="language-plaintext highlighter-rouge"&gt;Editor Settings &amp;gt; General &amp;gt; Text Editor &amp;gt; External&lt;/code&gt;.
There, you will need to tick the box &lt;code class="language-plaintext highlighter-rouge"&gt;Use external editor&lt;/code&gt;, indicate your Neovim installation path, and use &lt;code class="language-plaintext highlighter-rouge"&gt;--server /tmp/godothost --remote-send "&amp;lt;C-\&amp;gt;&amp;lt;C-N&amp;gt;:n {file}&amp;lt;CR&amp;gt;{line}G{col}|"&lt;/code&gt; as execution flags.&lt;/p&gt;

&lt;figure&gt;
	&lt;img alt="The editor settings" src="/assets/2024-11-01-godot-nvim/editor.jpg" width="100%" /&gt;
&lt;/figure&gt;

&lt;p&gt;While in the settings, head to &lt;code class="language-plaintext highlighter-rouge"&gt;Network &amp;gt; Language Server&lt;/code&gt; and note down the remote port Godot is using.
By default, it should be &lt;em&gt;6005&lt;/em&gt;.
We will need that value later.&lt;/p&gt;

&lt;h1 id="connecting-to-godot-with-vim-godot"&gt;Connecting to Godot with vim-godot&lt;/h1&gt;

&lt;p&gt;Neovim will be able to access Godot features by using a plugin called vim-godot.
We will need to edit the nvim configuration file to install plugins and configure Neovim.
On Mac and Linux, it is located at &lt;code class="language-plaintext highlighter-rouge"&gt;~/.config/nvim/init.vim&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;I use &lt;a href="https://github.com/junegunn/vim-plug"&gt;vim-plug&lt;/a&gt; to manage my plugins, so I can just add it to my configuration like this:&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;call plug#begin('~/.vim/plugged')
" ...
Plug 'habamax/vim-godot'
" ...
call plug#end()
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Once the configuration file is modified and saved, use the &lt;code class="language-plaintext highlighter-rouge"&gt;:PlugInstall&lt;/code&gt; command to install it.&lt;/p&gt;

&lt;p&gt;You’ll also need to indicate Godot’s executable path. Add this line to your &lt;em&gt;init.vim&lt;/em&gt;:&lt;/p&gt;
&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;let g:godot_executable = '/Applications/Godot.app/Contents/MacOS/Godot'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For vim-godot to communicate with the Godot editor, it will need to listen to the &lt;code class="language-plaintext highlighter-rouge"&gt;/tmp/godothost&lt;/code&gt; file we configured in the editor previously.
To do that, simply launch nvim with the flag &lt;code class="language-plaintext highlighter-rouge"&gt;--listen /tmp/godothost&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To save you some precious keypress, I suggest creating a new alias in your &lt;em&gt;bashrc&lt;/em&gt;/&lt;em&gt;zshrc&lt;/em&gt; like this:&lt;/p&gt;
&lt;div class="language-sh highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;gvim&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"nvim --listen /tmp/godothost"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id="getting-autocompletion-with-cocnvim"&gt;Getting autocompletion with coc.nvim&lt;/h1&gt;

&lt;p&gt;Godot ships with a language server.
It means the Godot editor can provide autocompletion, syntax highlighting, and advanced navigation to external editors like nvim.&lt;/p&gt;

&lt;p&gt;While Neovim now has built-in support for the language server protocol, I’ve used the plugin &lt;code class="language-plaintext highlighter-rouge"&gt;coc.nvim&lt;/code&gt; to obtain these functionalities for years and see no reason to change.
You can also install it with vim-plug by adding the following line to your plugin list:&lt;/p&gt;
&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;Plug 'neoclide/coc.nvim', {'branch':'release'}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Run &lt;code class="language-plaintext highlighter-rouge"&gt;:PlugInstall&lt;/code&gt; again to install it.&lt;/p&gt;

&lt;p&gt;You’ll need to indicate the Godot language server address and port using the command &lt;code class="language-plaintext highlighter-rouge"&gt;:CocConfig&lt;/code&gt;.
It should open Coc’s configuration file, which is a JSON file normally located at &lt;code class="language-plaintext highlighter-rouge"&gt;~/.config/nvim/coc-settings.json&lt;/code&gt;.
In this file enter the following data, and make sure the port number matches the one located in your editor:&lt;/p&gt;

&lt;div class="language-json highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"languageserver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"godot"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"filetypes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"gdscript"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6005&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I recommend adding Coc’s example configuration to your &lt;em&gt;init.vim&lt;/em&gt; file.
You can &lt;a href="https://github.com/neoclide/coc.nvim?tab=readme-ov-file#example-vim-configuration"&gt;find it on GitHub&lt;/a&gt;.
It will provide you with a lot of useful shortcuts, such as using &lt;code class="language-plaintext highlighter-rouge"&gt;gd&lt;/code&gt; to go to a function definition and &lt;code class="language-plaintext highlighter-rouge"&gt;gr&lt;/code&gt; to list its references.&lt;/p&gt;

&lt;h1 id="debugging-using-nvim-dap"&gt;Debugging using nvim-dap&lt;/h1&gt;

&lt;p&gt;If you want to use the debugger from inside Neovim, you’ll need to install another plugin called nvim-dap.
Add the following to your plugins list:&lt;/p&gt;
&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;Plug 'mfussenegger/nvim-dap'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The plugin authors suggest configuring it using Lua, so let’s do that by adding the following in your &lt;em&gt;init.vim&lt;/em&gt;:&lt;/p&gt;
&lt;div class="language-lua highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="n"&gt;lua&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;dap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"dap"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;dap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;adapters&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;godot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
	&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
	&lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
	&lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;6006&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;dap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;configurations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gdscript&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
	&lt;span class="p"&gt;{&lt;/span&gt;
		&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"godot"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
		&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
		&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Launch scene"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
		&lt;span class="n"&gt;project&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${workspaceFolder}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
		&lt;span class="n"&gt;launch_scene&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
	&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_user_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Breakpoint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lua require'dap'.toggle_breakpoint()"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_user_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Continue"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lua require'dap'.continue()"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_user_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"StepOver"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lua require'dap'.step_over()"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_user_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"StepInto"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lua require'dap'.step_into()"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="n"&gt;vim&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;nvim_create_user_command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"REPL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"lua require'dap'.repl.open()"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
&lt;span class="n"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This will connect to the language server (here on port 6005), and allow you to pilot the debugger using the following commands:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;:Breakpoint&lt;/code&gt; to create (or remove) a breakpoint&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;:Continue&lt;/code&gt; to launch the game or run until the next breakpoint&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;:StepOver&lt;/code&gt; to step over a line&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;:StepInto&lt;/code&gt; to step inside a function definition&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;:REPL&lt;/code&gt; to launch a REPL (useful if you want to examine values)&lt;/li&gt;
&lt;/ul&gt;

&lt;h1 id="conclusion"&gt;Conclusion&lt;/h1&gt;

&lt;p&gt;I hope you’ll have a great time developing Godot games with Neovim.
If it helps you, you can check out my entire &lt;em&gt;init.vim&lt;/em&gt; file on &lt;a href="https://gist.github.com/zer0tonin/9c26bb0ddb8e581d1ce5984daeabda04"&gt;GitHub gist&lt;/a&gt;.&lt;/p&gt;</description><author>Alice GG</author><pubDate>Fri, 01 Nov 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://www.alicegg.tech//2024/11/01/godot-nvim.html</guid></item><item><title>The best laptop is the one somebody else had</title><link>https://ounapuu.ee/posts/2024/11/01/the-best-laptop/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/11/01/the-best-laptop/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;In 2011, I was finishing 9th grade. As a gift, I got to choose a laptop in the
400 EUR range. I ended up picking
an &lt;a href="https://www.laptopmag.com/reviews/laptops/asus-eee-pc-1201pn"&gt;ASUS Eee PC 1201PN.&lt;/a&gt; It was new and the
first computer in my life that was 100% &lt;em&gt;mine&lt;/em&gt;, but awfully slow for a lot of tasks.&lt;/p&gt;
&lt;p&gt;It was &lt;em&gt;&lt;strong&gt;so slow&lt;/strong&gt;&lt;/em&gt; that I ended up giving Linux a go as a result. &lt;em&gt;&lt;strong&gt;Linux!&lt;/strong&gt;&lt;/em&gt; I didn&amp;rsquo;t even know computing all that
well around that time!&lt;/p&gt;
&lt;p&gt;A few years later, I bought a ThinkPad T60 off of someone I knew for about &lt;strong&gt;40 EUR&lt;/strong&gt;. It was about 8 years old at that
point,
but it ran circles around the new laptop that I had in performance.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s when I learned about the absurdly good price-to-performance ratio of used business-grade laptops, and the
crappiness of netbooks.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Note that I keep repeating the phrase &lt;em&gt;&lt;strong&gt;business-grade&lt;/strong&gt;&lt;/em&gt; laptops. Think Lenovo ThinkPads, Dell Latitudes or HP
EliteBooks.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the core of this whole idea. Consumer-grade
laptops are cheaper when bought new, but that is a result of a lot of compromises made in the build quality.
Business-grade laptops are used for work and need to be reliable for years, which means that they will last for a long
time.&lt;/p&gt;
&lt;h3 id="used-laptops-are-cheap"&gt;Used laptops are cheap&lt;/h3&gt;
&lt;p&gt;I recently checked what the prices are for used laptops, mainly focusing on the 100-300 EUR range as I find that to
be the sweet spot for bargains.&lt;/p&gt;
&lt;p&gt;For 195 EUR, I can get a ThinkPad X395, sporting an AMD Ryzen 5 3500U quad-core CPU, 16 GB of RAM and a 256GB NVMe SSD,
sold by a store that specializes in selling used hardware. You even get a 6-month warranty! That&amp;rsquo;s crazy good value.&lt;/p&gt;
&lt;p&gt;New business-grade laptops cost somewhere around 1000-2000+ EUR. They are generally faster and provide more memory and
storage, but in the best case scenario that performance difference will be 2-3x at best, while the price is 5-10+ times
higher. The math does &lt;em&gt;not&lt;/em&gt; check out.&lt;/p&gt;
&lt;p&gt;The price depreciation curve is also quite harsh on new laptops. You can pay 2000 EUR for a new laptop and only be able
to
sell it for 1000 EUR a year from now. Two years later? 500-700 EUR. The prices eventually settle at around the 5 year
mark, which also happens to align with the extended warranties expiring.&lt;/p&gt;
&lt;h3 id="used-laptops-reduce-stress"&gt;Used laptops reduce stress&lt;/h3&gt;
&lt;p&gt;With used laptops, you don&amp;rsquo;t have to worry about the wear-and-tear that much.&lt;/p&gt;
&lt;p&gt;You accidentally drop your laptop on the floor? It might still be fine!&lt;/p&gt;
&lt;p&gt;Your child picked off all the keycaps on the keyboard? No worries, replacements are easy to find!&lt;/p&gt;
&lt;p&gt;Your lunch for the day ended up leaking all over the laptop, killing it completely? No problem, you can get a new one
and still end up paying less compared to a new laptop!&lt;/p&gt;
&lt;p&gt;Buying used is no excuse to mistreat your hardware, but I personally love the lack of stress associated with trying
to keep a new and expensive object in pristine condition. The laptop already has some cosmetic damage on it, so
why worry?&lt;/p&gt;
&lt;h3 id="used-laptops-are-surprisingly-reliable"&gt;Used laptops are surprisingly reliable&lt;/h3&gt;
&lt;p&gt;Reliability is often one of the top reasons why some people avoid buying used laptops. I attribute this to the
experiences
people have with used cars. You pay less, until you pay a lot more to get that hunk of junk fixed once it breaks down on
you.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve had the complete opposite experience with used business-grade laptops. The ones that make it to the used market
have gone through years of reliability testing, and those that don&amp;rsquo;t make it were defective anyway. The
only areas to pay attention to is basic maintenance (remove dust, apply new thermal paste) and a potential battery
replacement,
which are quite simple to do on modern business-grade laptops. It&amp;rsquo;s so easy
that &lt;a href="https://greendice.com/repair-cafe-with-concise-and-students-from-kindluse-school/"&gt;even children and teenagers can do it&lt;/a&gt;
with a little bit of guidance and supervision!&lt;/p&gt;
&lt;p&gt;The reliability doesn&amp;rsquo;t stop with the hardware. Buying used often means that you&amp;rsquo;ll be buying a laptop that has received
all the software and firmware
fixes &lt;a href="https://www.notebookcheck.net/Lenovo-statement-Thunderbolt-firmware-responsible-for-ThinkPad-USB-C-failures.451307.0.html"&gt;to all sorts of issues.&lt;/a&gt;
Linux users will also have a much better time with used laptops since by that time most of the issues associated with
new hardware will have been fixed in the kernel.&lt;/p&gt;
&lt;p&gt;You should avoid buying new &lt;em&gt;and&lt;/em&gt; used &lt;em&gt;consumer-grade&lt;/em&gt; laptops. I&amp;rsquo;ve seen so many of those with missing pieces of
plastic and the
hinges breaking open the laptop case,
but rarely with business-grade laptops.&lt;/p&gt;
&lt;p&gt;Used business-grade laptops are so reliable
that &lt;a href="https://greendice.com/"&gt;some companies are even willing to rent and support those machines for a really low price.&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="exceptions-to-the-rule"&gt;Exceptions to the rule&lt;/h3&gt;
&lt;p&gt;There will always be a place for new laptops.&lt;/p&gt;
&lt;p&gt;Sometimes you &lt;em&gt;do&lt;/em&gt; need the latest and greatest hardware for
CAD work, complex video editing or high-end gaming.&lt;/p&gt;
&lt;p&gt;Some people find that a 30-second build of their software project taking 20 seconds is worth the productivity gain,
regardless of the higher price or increased environmental impact of buying new.&lt;/p&gt;
&lt;p&gt;Some simply want to play around with the latest and greatest, for fun.&lt;/p&gt;
&lt;p&gt;There will always be people who find the idea of used laptops off-putting, and companies do prefer to buy pallet-loads
of new laptops every few years.&lt;/p&gt;
&lt;p&gt;On the bright side, this does mean that there will always be a supply of cheap used laptops available for the rest of
us.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;If you ever need a laptop and your needs are not extremely specific, then give a used business-grade laptop a
try. It will be fine, I promise.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;no hard feelings to my mom, we both didn&amp;rsquo;t know any better.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;note that there are certain models that are a ThinkPad in name only, under the hood it&amp;rsquo;s still the same
crappy components you see in consumer-grade laptops. Thanks, Lenovo.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>./techtipsy</author><pubDate>Fri, 01 Nov 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/11/01/the-best-laptop/</guid></item><item><title>2024.10.DisappearingMoment</title><link>https://newsletter.disappearingmoment.com/archive/202410disappearingmoment/</link><description>&lt;p&gt;What are the odds that the worst human was born between 1878 and 1893? The world population in 1945 was about 2.4 billion. If &lt;a href="https://www.prb.org/articles/how-many-people-have-ever-lived-on-earth/" target="_blank"&gt;117 billion people have been born&lt;/a&gt;, that sets the odds a touch over 2%.&lt;/p&gt;
&lt;p&gt;Of course, that doesn’t account for. Or. And then there’s. How many millions suffered and died because of?&lt;/p&gt;
&lt;p&gt;I don’t believe in the Great Man Theory. Modern atrocities rely on circumstance, abetting, credulity, and shock. They also rely on technology.&lt;/p&gt;
&lt;p&gt;Our ability to help and harm has magnified individual and collective responsibility. Factoring in technology, the possibility that the worst human was born between 1878 and 1893 is higher than 2%.&lt;/p&gt;
&lt;p&gt;In the last 80 years, technology has advanced by orders of magnitude. What are the odds that the worst human ever born is alive today? A touch under 7% of the people who have been born are alive. Accounting for technology means there’s a good chance that we’re living alongside history’s most vile human.&lt;/p&gt;
&lt;p&gt;Welcome to October 2024’s Disappearing Moment, an inventory of my experiences. I hope you enjoy it.&lt;/p&gt;
&lt;h2&gt;Podcasts&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.jewishvoiceforpeace.org/resource/diaspora-podcast/" target="_blank"&gt;Diaspora&lt;/a&gt; (I Loved It): Spoiler Alert: the list below only includes one entry under “N”. &lt;/p&gt;
&lt;h2&gt;Nerdy Software&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://brew.sh/" target="_blank"&gt;Homebrew&lt;/a&gt; makes it easier to install open source software. Some favorites: &lt;a href="https://www.ffmpeg.org/" target="_blank"&gt;Ffmpeg&lt;/a&gt;, &lt;a href="https://git-scm.com/" target="_blank"&gt;Git&lt;/a&gt;, &lt;a href="https://gohugo.io/" target="_blank"&gt;Hugo&lt;/a&gt;, &lt;a href="https://git.sr.ht/~xenrox/hut" target="_blank"&gt;Hut&lt;/a&gt;, and &lt;a href="https://librewolf.net/" target="_blank"&gt;LibreWolf&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Free Font&lt;/h2&gt;
&lt;p&gt;Bonnie Shover-Troup developed &lt;a href="https://www.lexend.com/" target="_blank"&gt;Lexend&lt;/a&gt; to “make reading easier for everyone.” She wants you to be in control, so you can "improve retention and comprehension.”&lt;/p&gt;
&lt;h2&gt;Bougie Products&lt;/h2&gt;
&lt;p&gt;Xero Shoes are great for the gym and good for minimal running. &lt;a href="https://xeroshoes.com/warranty/" target="_blank"&gt;No athletic shoes are better made&lt;/a&gt;. And they're having a big sale&lt;/p&gt;
&lt;h2&gt;Personal Finance and Investing&lt;/h2&gt;
&lt;p&gt;Personal finance is about mitigating risk. Do you have a rainy day fund in Euros, Swiss Francs, Yen, or AUD? In a non-US account?&lt;/p&gt;
&lt;h2&gt;Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Ahmad Alfy, “&lt;a href="https://alfy.blog/2024/10/19/linking-directly-to-web-page-content.html" target="_blank"&gt;Smarter than 'Ctrl+F': Linking Directly to Web Page Content&lt;/a&gt;” (I Liked It): “&lt;a href="https://alfy.blog/2024/10/19/linking-directly-to-web-page-content.html#:~:text=Text%20fragments%20are%20currently%20supported%20in%20all%20the%20browsers." target="_blank"&gt;Text fragments are currently supported in all the browsers&lt;/a&gt;.” I had no idea. This changes everything. See also, “&lt;a href="https://www.dont-click-here.com/" target="_blank"&gt;Don’t Click Here&lt;/a&gt;.”&lt;/li&gt;
&lt;li&gt;Loris Cro, “&lt;a href="https://kristoff.it/blog/static-site-paradox/" target="_blank"&gt;The Static Site Paradox&lt;/a&gt;” (I Liked It): I can no longer recommend &lt;a href="https://techcrunch.com/2024/10/17/permira-completes-squarespace-acquisition-after-upping-bid-to-7-2b/" target="_blank"&gt;Squarespace&lt;/a&gt; or &lt;a href="https://techcrunch.com/2024/10/29/wordpress-vs-wp-engine-drama-explained/" target="_blank"&gt;WordPress&lt;/a&gt;. Can we simplify serving &lt;a href="https://newsletter.disappearingmoment.com/archive/202409disappearingmoment/#:~:text=I%20use%20Hugo%2C%20a%20Content%20Management%20System%2C%20to%20organize%20the%20Disappearing%20Moment%20website." target="_blank"&gt;Hugo&lt;/a&gt; sites from anti-VC hosts? E.g., “&lt;a href="https://blakewatson.com/journal/the-making-of-html-for-people/" target="_blank"&gt;The Making of HTML for People&lt;/a&gt;.”&lt;/li&gt;
&lt;li&gt;Kathryn S. McKinley, “&lt;a href="https://www.sigarch.org/what-happens-to-us-does-not-happen-to-most-of-you/" target="_blank"&gt;What Happens to Us Does Not Happen to Most of You&lt;/a&gt;” (I Liked It): A reminder to track what I listen to and read. To pay attention to people whose experiences are not like mine. To work for change.&lt;/li&gt;
&lt;li&gt;Jenn Schiffer, "&lt;a href="https://livelaugh.blog/posts/falling-for-princes-fridge/" target="_blank"&gt;Falling for Prince's Fridge&lt;/a&gt;" (I Loved It): Accuracy is ephemeral. Satire is everlasting.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Worst Human&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Omar al-Bashir or Samuel Alito or Bashar al-Assad&lt;/li&gt;
&lt;li&gt;Steve Bannon or Jair Bolsonaro&lt;/li&gt;
&lt;li&gt;Tucker Carlson or Dick Cheney &lt;/li&gt;
&lt;li&gt;Paul Dans or Thomas DiLorenzo&lt;/li&gt;
&lt;li&gt;Recep Tayyip Erdoğan&lt;/li&gt;
&lt;li&gt;Patrick Falte and Benjamin Faulkner&lt;/li&gt;
&lt;li&gt;Newt Gingrich or Marjorie Taylor Greene &lt;/li&gt;
&lt;li&gt;Josh Hawley or Min Aung Hlaing&lt;/li&gt;
&lt;li&gt;Laura Ingraham&lt;/li&gt;
&lt;li&gt;Mike Johnson or Jerry Jones&lt;/li&gt;
&lt;li&gt;Kim Jong Un or Charles Koch&lt;/li&gt;
&lt;li&gt;Debbie Lesko&lt;/li&gt;
&lt;li&gt;Rupert Murdoch or Elon Musk&lt;/li&gt;
&lt;li&gt;Benjamin Netenyahu&lt;/li&gt;
&lt;li&gt;Andy Ogles or Bill O'Reilly&lt;/li&gt;
&lt;li&gt;Vladimir Putin&lt;/li&gt;
&lt;li&gt;Q&lt;/li&gt;
&lt;li&gt;Kevin Roberts or Karl Rove&lt;/li&gt;
&lt;li&gt;David D. Smith or Daniel Stein&lt;/li&gt;
&lt;li&gt;Donald Trump&lt;/li&gt;
&lt;li&gt;Richard and Elizabeth Uihlein&lt;/li&gt;
&lt;li&gt;J.D. Vance&lt;/li&gt;
&lt;li&gt;Andrew Wakefield or Randy Weber&lt;/li&gt;
&lt;li&gt;Xi Jinping&lt;/li&gt;
&lt;li&gt;Ramzi Yousef&lt;/li&gt;
&lt;li&gt;Mark Zuckerberg&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you for spending a few moments with me. I appreciate you and look forward to corresponding again next month.&lt;/p&gt;
&lt;p&gt;Brett&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want to discuss any of the topics in this newsletter or anything else with other Disappearing Moment readers? Please sign up for &lt;a href="https://august.disappearingmoment.com/" target="_blank"&gt;Perpetual August&lt;/a&gt;. I think it might be fun.&lt;/em&gt;&lt;/p&gt;</description><author>Disappearing Moment</author><pubDate>Fri, 01 Nov 2024 04:16:05 GMT</pubDate><guid isPermaLink="true">https://newsletter.disappearingmoment.com/archive/202410disappearingmoment/</guid></item><item><title>MX-518 Repair Guide</title><link>https://cookie.engineer/weblog/articles/mx518-repair-guide.html</link><description>Repair Guide for the best mouse ever produced, the Logitech MX-518</description><author>Cookie Engineer's Web Log</author><pubDate>Fri, 01 Nov 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://cookie.engineer/weblog/articles/mx518-repair-guide.html</guid></item><item><title>November 2024 - Bookmarks</title><link>https://domenicoluciani.com/2024/11/01/bookmarks.html</link><description>Bookmarks for November 2024: 6 links - What is a Staff Engineer?; Please just stop saying "just"; Breaking Down Tasks - Jacob Kaplan-Moss, and more.</description><author>Domenico Luciani</author><pubDate>Fri, 01 Nov 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://domenicoluciani.com/2024/11/01/bookmarks.html</guid></item><item><title>Maintenance is Underestimated</title><link>https://bastian.rieck.me/blog/2024/maintenance/</link><description>&lt;p&gt;Last year, with a baby on the way, my wife and I decided to move from
our apartment into a house. We are now more or less our own landlords,
which is nice, but at the same time, we also have to spend substantial
amounts of time just &lt;em&gt;maintaining&lt;/em&gt; everything. Grass is growing on our
terrace; we have to remove it. The heating systems needs a check-up: I
schedule an appointment. An outlet is not working properly: let&amp;rsquo;s call
an electrician.&lt;/p&gt;
&lt;p&gt;Nothing surprising about this so far, but it got me thinking: all such
tasks are incredibly &lt;em&gt;valuable&lt;/em&gt; but also time-consuming. It&amp;rsquo;s tempting
to &lt;em&gt;not&lt;/em&gt; do them. But, over time, problems would pile up, and then, at
some point in the future, several critical systems would be failing. I
do not want to have the heater break down in the middle of winter&amp;mdash;so
I of course do preventative maintenance, and so on.&lt;/p&gt;
&lt;p&gt;At some point, weeding the garden, it hit me: most of the things we
typically associate with &amp;rsquo;the good life&amp;rsquo; are about maintenance. You
have to maintain your relationships lest they wither. You also need
to maintain your (institutional) responsibilities, like checking in
with the rules and regulations of your university regularly&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;. It
is not always the most glamorous work&amp;mdash;who likes to clean old code
instead of writing new, for instance&amp;mdash;but it &lt;strong&gt;has to be done&lt;/strong&gt;. I
do not want to be confronted with an overgrown garden of code, or a
friendship in shambles because I did not invest in its maintenance.&lt;/p&gt;
&lt;p&gt;Of course, not all types of maintenance are equal. Weeding a garden
brings me substantially less joy than checking in with a friend. It
is, however, also a fact of life that maintaining something good is
at least &lt;em&gt;some&lt;/em&gt; work. We are primed to think of life happening in a
sequence of big, highly important events&amp;mdash;and there is certainly a
sliver of truth here. Nevertheless, most of life are just ordinary,
but equally relevant events, and they should be celebrated as such.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s to maintaining everything, until next time!&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Of course, academia is not the only area that has this.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Thu, 31 Oct 2024 18:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2024/maintenance/</guid></item><item><title>Setting up pre-commit for the forgetful</title><link>https://jay.jvf.cc/posts/openstack-pre-commit/</link><description>&lt;h1 id="openstack--pre-commit"&gt;OpenStack + pre-commit&lt;/h1&gt;
&lt;p&gt;Many OpenStack projects, including Ironic and many SDK and oslo projects, now
utilize &lt;a href="https://pre-commit.com"&gt;pre-commit&lt;/a&gt; to perform linting at commit time.&lt;/p&gt;
&lt;p&gt;This tool has a major downside: git hooks have to be enabled locally. If
you&amp;rsquo;re someone who can easily forget things, and works in many diverse repos,
this can lead to not utilizing this tool, which is unfortunate.&lt;/p&gt;
&lt;p&gt;Luckily, I&amp;rsquo;ve found a solution! Templates are supported for git configs when
you &lt;code&gt;git clone&lt;/code&gt; or &lt;code&gt;git init&lt;/code&gt; a repo, these are located. This means we can
enable a pre-commit hook by default in all our repos:&lt;/p&gt;</description><author/><pubDate>Thu, 31 Oct 2024 17:45:52 GMT</pubDate><guid isPermaLink="true">https://jay.jvf.cc/posts/openstack-pre-commit/</guid></item><item><title>At-will employment</title><link>https://www.kcoleman.me/2024/10/31/at-will.html</link><description>Before I joined Grab, USA’s At-will employment system left me uncomfortable. When I was impacted by layoffs, I simultaneously balanced the emotional disruption of establishing a new identity (“I was no longer Kevin that works at ___”, but “fUnemployed Kevin”) and the need to re-evaluate my finances and healthcare. A 30-day or 60-day notice would have given me a softer landing while I rebuild myself. But my experiences as a hiring manager at an international company shift my perspective on what is actually best for the employee (and the employer). As a hiring manager, I’ve hired and said good-bye to co-workers and directs from India, Malaysia, and the USA countries in contract employment and at-will roles. At-will in the USA In late 2020, I wanted to leave Yelp as soon as possible. When I got a job offer from Grab, I provided Yelp the standard 2 weeks notice, last day being Friday and then Grab started me the following Monday. This rapid change had perfect timing, because my new-hire grant was priced about 40% below our IPO price a year later. Coworkers that joined during the pre-ipo period (7 months after I did) had their shares valued at ~$10 IPO price (currently trading at $4.10/ea). Most of the time, Start your vesting as soon as possible is the best strategy. Contracted termination in Singapore When I got my first big project at Grab, I was assigned a product manager that halfway through the project put in her 60 day notice to leave Grab. During those 60 days, you’re expected to perform your full job duties until the day you leave. Because the project was scheduled to end 2 weeks before her last day, her manager didn’t bother transitioning her responsibilities to another person. Unfortunately for me, she was visibly disengaged with her work. I wasn’t able to get the answers I needed at the rate I needed them, and thus couldn’t deliver the project on time. She effectively had 2 months of part-time labor for full time pay with her coworkers paying the price with their own careers. Hiring in India A coworker give a 60 day notice to leave the company. He stayed engaged until his last week, which everyone appreciated, but hiring a backfill was a hot mess. I don’t know if internal bookkeeping delayed searching for a backfill, but it wasn’t 2 months after he left did we find a replacement. Unfortunately, this replacement also had a 60 day notice they needed to give their current employer. Our group made due with a hole in our group for 4 months (2 months hiring + 2 months waiting for them to join + 1.5 months of training). 10 days before their start date, the new hire messaged the company saying their current employer gave them a raise once they saw Grab’s offer and would not be joining. With the role being potentially vacant for an additional 4 months, the manager ended up just losing the headcount. Hiring in Malaysia A team member in India informed me they wanted to leave the company and put in their 60 day notice. I negotiated with People Operations and my manager to terminate him immediately (but keep him on payroll for 60 days to meet the contract obligations). After my experience with the lady in Singapore, I thought a visible hole on my team would be less painful than trying to get someone that doesn’t want to be here to complete their work. I communicated to other teams that we didn’t have the bandwidth to deliver on our project commitments because he was gone. I opted to backfill the role in Malaysia, for various reasons. After 2 months of interviewing, we found a great candidate. Unfortunately, this person also had a 2 month contract that they needed to complete, resulting in a 5 month hole on my team between one person leaving and another person coming in. Hire and Fire With contract employment, there is a probation period ranging from 3-6 months. During this time, the employment is practically “at-will” where the employer can terminate an employee instantly without the 60 day notice period. This system incentives companies with teams of many redundant employees (for example call centers) to “hire and fire” during that period if anything smells wrong, because if they just wait 1 day, they could be stuck with an underperformer for months. As difficult as at-will employment might feel sometimes, it is much more efficient for the employee and the employer if they are able to leave the job when they want to leave the job. Employees maximize their engagement (and career growth) if they are able to shift onto their next chapter faster and employers can better manage</description><author>Kevin Coleman</author><pubDate>Thu, 31 Oct 2024 07:49:55 GMT</pubDate><guid isPermaLink="true">https://www.kcoleman.me/2024/10/31/at-will.html</guid></item><item><title>Fast</title><link>https://solomon.io/fast/</link><description>In early October, I gave a presentation on platform thinking at an onsite for Salesloft’s product experience team—what it is and what it enables.</description><author>Sam Solomon</author><pubDate>Thu, 31 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://solomon.io/fast/</guid></item><item><title>Checking linearizability in Go</title><link>http://notes.eatonphil.com/2024-10-31-checking-linearizability-in-go.html</link><description>&lt;p&gt;&lt;!-- -*- mode: markdown -*- --&gt;&lt;/p&gt;
&lt;p&gt;You want to check for strict consistency
(&lt;a href="https://jepsen.io/consistency/models/linearizable"&gt;linearizability&lt;/a&gt;)
for your project but you don't want to have to &lt;a href="https://github.com/jepsen-io/"&gt;deal with the
JVM&lt;/a&gt;. &lt;a href="https://github.com/anishathalye/porcupine"&gt;Porcupine&lt;/a&gt;,
used by a number of real-world systems like etcd and TiDB, has you
covered!&lt;/p&gt;
&lt;p&gt;Importantly, neither Jepsen projects nor Porcupine can &lt;em&gt;prove&lt;/em&gt;
linearizability. They can only help you &lt;em&gt;build confidence&lt;/em&gt; that you
aren't obviously &lt;em&gt;violating&lt;/em&gt; linearizability.&lt;/p&gt;
&lt;p&gt;The Porcupine README is pretty good but doesn't give complete working
code, so I'm going to walk through checking linearizability of a
distributed register. And then we'll tweak things a bit by checking
linearizability for a distributed key-value store.&lt;/p&gt;
&lt;p&gt;But rather than implementing a distributed register and implementing a
distributed key-value store, to keep this post concise, we're just
going to imagine that they exist and we'll come up with some example
histories we might see.&lt;/p&gt;
&lt;p&gt;Code for this post can be found on
&lt;a href="https://github.com/eatonphil/linearizability-playground"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="boilerplate"&gt;Boilerplate&lt;/h3&gt;&lt;p&gt;Create a new directory and &lt;code&gt;go mod init lintest&lt;/code&gt;. Let's add the
imports we need and a helper function for generating a visualization
of a history, in &lt;code&gt;main.go&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;log&amp;quot;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;github.com/anishathalye/porcupine&amp;quot;&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;visualizeTempFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LinearizationInfo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CreateTemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;*.html&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;failed to create temp file&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Visualize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;visualization failed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;wrote visualization to %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h3 id="a-distributed-register"&gt;A distributed register&lt;/h3&gt;&lt;p&gt;A distributed register is like a distributed key-value store but
there's only a single key.&lt;/p&gt;
&lt;p&gt;We need to tell Porcupine what the inputs and outputs for this system
are. And we'll later describe for it how an idealized version of this
system should behave as it receives each input; what output the
idealized version should produce.&lt;/p&gt;
&lt;p&gt;Each time we send a command to the distributed register it will
include an operation (to get or to set the register). And if it is a
set command it will include a value.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// &amp;quot;get&amp;quot; and &amp;quot;set&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The register is an integer register.&lt;/p&gt;
&lt;p&gt;Now we will define a model for Porcupine which, again, is the
idealized version of this system.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;registerModel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;porcupine&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateAny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inputAny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputAny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inputAny&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputAny&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stateAny&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb nb-Type"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;readCorrectValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readCorrectValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Unexpected operation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The step function accepts anything because it has to be able to model
any sort of system with its different inputs and outputs and current
state. So we have to handle casting from the &lt;code&gt;any&lt;/code&gt; type to what we
know are the inputs and outputs and state. And finally we actually do
the state change and return the new state as well as if the given
output matches what we know it should be.&lt;/p&gt;
&lt;h3 id="an-invalid-history"&gt;An invalid history&lt;/h3&gt;&lt;p&gt;Now we've only defined the idealized version of this system. Let's
pretend we have some real-world implementation of this. We might have
two clients and they might issue concurrent get and set requests.&lt;/p&gt;
&lt;p&gt;Every time we stimulate the system we will generate a new history that
we can validate with Porcupine against our model to see if the history
is linearizable.&lt;/p&gt;
&lt;p&gt;Let's imagine these two clients concurrently set the register to some
value. Both sets succeed. Then both clients read the register. And
they get different values. Here's what that history would look like
modeled for Porcupine.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 sets the register to 100. The request starts at t0 and ends at t2.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* end state at t2 is 100 */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 sets the register to 200. The request starts at t3 and ends at t4.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="cm"&gt;/* end state at t3 is 200 */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 reads the register. The request starts at t5 and ends at t6.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 reads the register. The request starts at t7 and ends at t8. Reads a stale value!&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CheckOperationsVerbose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registerModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;visualizeTempFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;registerModel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected operations to be linearizable&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;If we build and run this code:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;mod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;tidy&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;finding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;anishathalye&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;porcupine&lt;/span&gt;
&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;found&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;anishathalye&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;porcupine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;anishathalye&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;porcupine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="mf"&gt;.1.6&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt;
&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lintest&lt;/span&gt;
&lt;span class="mi"&gt;2024&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;54&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;08&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;wrote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;visualization&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nf"&gt;var&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;folders&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v27m749d0sj89h9ydfq0f0940000gn&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;463308000.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt;
&lt;span class="nl"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;operations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;linearizable&lt;/span&gt;

&lt;span class="n"&gt;goroutine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;running&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Users&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;phil&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tmp&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lintest&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;59&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mh"&gt;0x394&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Porcupine caught the stale value. Open that HTML file to see
the visualization.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/bad-register-history.png" src="/assets/bad-register-history.png" /&gt;&lt;/p&gt;
&lt;h3 id="a-valid-history"&gt;A valid history&lt;/h3&gt;&lt;p&gt;Let's say we fix the bug so now there's no stale read. The new history
would look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 sets the register to 100. The request starts at t0 and ends at t2.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* end state at t2 is 100 */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 sets the register to 200. The request starts at t3 and ends at t4.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="cm"&gt;/* end state at t3 is 200 */&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 reads the register. The request starts at t5 and ends at t6.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 reads the register. The request starts at t7 and ends at t8.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;registerInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Rebuild, rerun &lt;code&gt;lintest&lt;/code&gt; (it should exit successfully now), and open
the visualization.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/good-register-history.png" src="/assets/good-register-history.png" /&gt;&lt;/p&gt;
&lt;p&gt;Great! Now let's make things a little more complicated by modeling a
distributed key-value store rather than a distributed register.&lt;/p&gt;
&lt;h3 id="distributed-key-value"&gt;Distributed key-value&lt;/h3&gt;&lt;p&gt;The inputs of this system will be slightly more complex. They will
take a &lt;code&gt;key&lt;/code&gt; along with the &lt;code&gt;operation&lt;/code&gt; and &lt;code&gt;value&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kvInput&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// &amp;quot;get&amp;quot; and &amp;quot;set&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And when we model the distributed key-value store with the state and
output at each step being a &lt;code&gt;map[string]int&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;kvModel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Model&lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;Init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nc"&gt;int&lt;/span&gt;&lt;span class="err"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nl"&gt;Step&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stateAny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inputAny&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputAny&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;inputAny&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="n"&gt;kvInput&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputAny&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nc"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;stateAny&lt;/span&gt;&lt;span class="p"&gt;.(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nc"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;newState&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="nc"&gt;int&lt;/span&gt;&lt;span class="err"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="n"&gt;newState&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;newState&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;input.key&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;newState&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;operation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="n"&gt;readCorrectValue&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;output&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;input.key&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;input.key&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;readCorrectValue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;state&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;&amp;quot;Unexpected operation&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And now the history gets slightly more complex because we are now
working with some specific key. But we'll otherwise use the same
history as before.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ops&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="nx"&gt;porcupine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Operation&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 set key `a` to 100. The request starts at t0 and ends at t2.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kvInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 set key `a` to 200. The request starts at t3 and ends at t4.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kvInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;set&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 3 read key `a`. The request starts at t5 and ends at t6.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kvInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Client 5 read key `a`. The request starts at t7 and ends at t8.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;kvInput&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;get&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cm"&gt;/* doesn't matter */&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Build and run. Open the visualization.&lt;/p&gt;
&lt;p&gt;&lt;img alt="/assets/good-kv-history.png" src="/assets/good-kv-history.png" /&gt;&lt;/p&gt;
&lt;p&gt;And there we go!&lt;/p&gt;
&lt;h3 id="what's-next"&gt;What's next&lt;/h3&gt;&lt;p&gt;These are just a few simple examples that are not hooked up to a real
system. But it still seemed useful to show how you model one or two
simple different systems and check a history with Porcupine.&lt;/p&gt;
&lt;p&gt;Another aspect of Porcupine I did not cover is partitioning the state
space. The
&lt;a href="https://pkg.go.dev/github.com/anishathalye/porcupine#Model"&gt;docs&lt;/a&gt;
say:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Implementing the partition functions can greatly improve
performance. If you're implementing the partition function, the
model Init and Step functions can be per-partition. For example, if
your specification is for a key-value store and you partition by
key, then the per-partition state representation can just be a
single value rather than a map.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Perhaps that, and hooking this up to some "real" system, would be a
good next step.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a short tutorial on using Porcupine to check for linearizability (without needing to deal with the JVM).&lt;a href="https://t.co/kqeBz2jX76"&gt;https://t.co/kqeBz2jX76&lt;/a&gt; &lt;a href="https://t.co/teXvlp2zcv"&gt;pic.twitter.com/teXvlp2zcv&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1852143540131844109?ref_src=twsrc%5Etfw"&gt;November 1, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Thu, 31 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-10-31-checking-linearizability-in-go.html</guid></item><item><title>Revisiting the Outbox Pattern</title><link>https://www.morling.dev/blog/revisiting-the-outbox-pattern/</link><description>&lt;div class="toc" id="toc"&gt;
&lt;div id="toctitle"&gt;Table of Contents&lt;/div&gt;
&lt;ul class="sectlevel1"&gt;
&lt;li&gt;&lt;a href="#_recap_whats_the_outbox_pattern"&gt;Recap: What’s The Outbox Pattern?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_implementation_considerations"&gt;Implementation Considerations&lt;/a&gt;
&lt;ul class="sectlevel2"&gt;
&lt;li&gt;&lt;a href="#_polling_vs_log_based_cdc"&gt;Polling vs. Log-Based CDC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_the_outbox_table"&gt;The Outbox Table&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_pg_logical_emit_message"&gt;pg_logical_emit_message()&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_format_considerations"&gt;Format Considerations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_backfills"&gt;Backfills&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_idempotency_for_consumers"&gt;Idempotency for Consumers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#_criticisms_of_the_outbox_pattern"&gt;Criticisms of the Outbox pattern&lt;/a&gt;
&lt;ul class="sectlevel2"&gt;
&lt;li&gt;&lt;a href="#_database_overhead"&gt;Database Overhead&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_complexity"&gt;Complexity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_latency"&gt;Latency&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_discussion"&gt;Discussion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#_alternatives_to_the_outbox_pattern"&gt;Alternatives to the Outbox Pattern&lt;/a&gt;
&lt;ul class="sectlevel2"&gt;
&lt;li&gt;&lt;a href="#_dapr"&gt;Dapr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_read_yourself"&gt;Read-yourself&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_stream_processing_on_raw_change_event_streams"&gt;Stream Processing on "Raw" Change Event Streams&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_2pc"&gt;2PC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_durable_execution"&gt;Durable Execution&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="#_summary"&gt;Summary&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;&lt;em&gt;This post originally appeared on the &lt;a href="https://www.decodable.co/blog/revisiting-the-outbox-pattern"&gt;Decodable blog&lt;/a&gt;. All rights reserved.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Over the last few years, the outbox pattern has become a common solution for implementing data exchange flows between microservices.
It allows services to safely and reliably update their own local datastore and at the same time send out notifications to other services via data streaming platforms such as Apache Kafka.
But time isn’t standing still: people ask about disadvantages of the pattern (&lt;em&gt;is the database becoming a bottleneck?&lt;/em&gt;), alternative solutions such as "listen-to-yourself" have been proposed, Kafka is about to get support for participating in 2-phase commit (2PC) transactions, and more.
It’s time to take another look at the outbox pattern, what it is and how to implement it, whether it’s still relevant in 2024, and which alternatives exist!&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Thu, 31 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/revisiting-the-outbox-pattern/</guid></item><item><title>Longhorn Trophy</title><link>https://karmanyaah.malhotra.cc/projects/2024/04/trophy/</link><description>I CADed and soldered this 3D printed trophy with Neopixels in one day!</description><author>Karmanyaah Malhotra</author><pubDate>Wed, 30 Oct 2024 09:13:20 GMT</pubDate><guid isPermaLink="true">https://karmanyaah.malhotra.cc/projects/2024/04/trophy/</guid></item><item><title>VexU Over Under Competition</title><link>https://karmanyaah.malhotra.cc/projects/2024/06/vexu/</link><description>We won the Vex AI World Championship!</description><author>Karmanyaah Malhotra</author><pubDate>Wed, 30 Oct 2024 08:37:24 GMT</pubDate><guid isPermaLink="true">https://karmanyaah.malhotra.cc/projects/2024/06/vexu/</guid></item><item><title>Mathematical Analysis of the Royal Game of Ur</title><link>https://ztoz.blog/posts/math-analysis-royal-game-ur/</link><description>&lt;p&gt;From the &lt;a href="https://sciendo.com/journal/BGS"&gt;Board Games Study Journal&lt;/a&gt;:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://doi.org/10.2478/bgs-2023-0001"&gt;Mathematical analysis of the Royal Game of Ur&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Despite many discoveries and proposals for rules for the ancient board game known as the Royal Game of Ur (RGU), no mathematical analysis has yet been performed investigating those rules. In an attempt to fill that gap, this paper presents an initial mathematical analysis of the RGU from an introductory point of view. The paper deduces the overall complexity of the RGU using a state-space and game-tree complexity analysis, allowing the RGU to be compared to the popular games Checkers, Backgammon, Ludo, Chess, and Go. The paper builds upon the fundamental laws of combinatorics and probability to improve the understanding of the game: what patterns should you expect, what moves increase your chance to win, and what moves should you avoid. The paper also presents theorems to predict the probability of future dice rolls and piece movements within the game, allowing basic inferences to be made about strategy in the RGU. The game is further examined by analysing three different influences when determining the best move: advancement and attack (beneficial to the player), and captures (detrimental to the player). These influences are used to deduce explicit equations for the advantage gained by playing each possible move from a position, which allows the formalization of a strategic algorithm to play the RGU.&lt;/p&gt;
&lt;/blockquote&gt;</description><author>ℤ→ℤ</author><pubDate>Tue, 29 Oct 2024 22:39:36 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/math-analysis-royal-game-ur/</guid></item><item><title>My Art And Color-After Tiling</title><link>https://thecodist.com/my-art-and-color-after-tiling/</link><description>&lt;p&gt;I make &lt;a href="https://digcon.art/?ref=thecodist.com"&gt;generative art&lt;/a&gt; with Swift and use tiling in many pieces. &lt;a href="https://en.wikipedia.org/wiki/Truchet_tiles?ref=thecodist.com"&gt;Truchet tiles&lt;/a&gt; are generally arranged randomly and contain everything appearing in the final image. What I do differently is to separate the layout of tiles from colorizing the image. I call this technique &amp;quot;Color-After Tiling.&amp;quot;&lt;/p&gt;&lt;p&gt;For&lt;/p&gt;</description><author>The Codist</author><pubDate>Tue, 29 Oct 2024 21:39:02 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/my-art-and-color-after-tiling/</guid></item><item><title>Before You Can Have Smalltalk, You Must First Defeat Capitalism.</title><link>https://www.mgaudet.ca/blog/2024/10/28/before-you-can-have-smalltalk-you-must-first-defeat-capitalism</link><description>&lt;p&gt;At &lt;a href="https://2024.splashcon.org/"&gt;SPLASH 2024&lt;/a&gt; there were a few talks and sessions that felt a bit like lamentations. &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Richard_P._Gabriel"&gt;Richard P. Gabriel’s&lt;/a&gt; keynote&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/live/_VF3pISRYRc?feature=shared&amp;amp;t=270"&gt;Alan Kay’s interview&lt;/a&gt; with &lt;a href="https://en.wikipedia.org/wiki/Gilad_Bracha"&gt;Gilad Bracha&lt;/a&gt;, as part of REBASE&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/live/_VF3pISRYRc?feature=shared&amp;amp;t=16334"&gt;Bracha’s own talk later in the day&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Konrad Hinsen’s &lt;a href="https://dl.acm.org/doi/10.1145/3689492.3689808"&gt;Onward! essay&lt;/a&gt; talk.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A common theme across these was that abandonment of “Programming Systems” and the rise of systems without elegance, introspection, trust or capability. &lt;/p&gt;
&lt;p&gt;At the end of Hinsen’s talk, I &lt;a href="https://discuss.systems/@mgaudet/113369517464493647"&gt;posted on Mastodon&lt;/a&gt;: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The lamentations about the state technology at SPLASH are all lamentations of Capitalism.&lt;/p&gt;
&lt;p&gt;Only a few come close to saying it explicitly tho.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I actually think &lt;a href="https://www.youtube.com/embed/_VF3pISRYRc?start=3820&amp;amp;end=4139"&gt;Alan Kay came closest&lt;/a&gt;: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Wow, what a terrible waste of these kids lives to have an undergraduate degree in computing. Because think about what they could actually be learning, if they weren’t taking up all this time learning almost nothing interesting. And that learning they would do  would actually make them much more sensitive to the new things they could be learning about.&lt;/p&gt;
&lt;p&gt;And, of course, that’s not the whole story, because, let’s face it, things are very different than they were in the 60s and 70s, especially with regards to life-affecting things like, for instance, real-estate prices which are not counted in inflation, and in California have been more than a factor of ten, over regular inflation. And this has changed the goal structure of lots of kids going to college. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So, why don’t people build these sorts of systems anymore? Why is research focused on so many industrial applications? Why is it build-build-build? Why are universities failing to be bastions of knowledge creation? &lt;/p&gt;
&lt;p&gt;Capitalism. Maybe too pithy. &lt;/p&gt;
&lt;p&gt;More specifically, the problem is the precarity &amp;amp; lack of surplus of modern unrestricted rentier capitalism. Graduate students struggle with &lt;a href="https://www.ucop.edu/enrollment-services/data-and-reporting/student-budget-tables/typical-housing-costs-near-uc-campuses-2024.pdf"&gt;high rents&lt;/a&gt;, low stipends, heavy work loads and big loans. Lecturers have full teaching loads and are paid very little. Professors are writing grants to fund students, and no one seems to have any time to sit and think and play and produce beautiful results. &lt;/p&gt;
&lt;p&gt;I talked to an undergraduate student at SPLASH, and he was telling us about some neat work he helped with to do LLMs and code auto-completion. The attendee next to me after hearing his brief pitch immediately asked “Are you planning on commercializing this?”, and I died a little inside. More, when the undergrad student replied in the affirmative. &lt;/p&gt;
&lt;p&gt;The title of this blog post is at least a little sarcastic, but I think is an honest to goodness truth: If you want to build a future that involves beautiful systems like Smalltalk, you must first rebuild the economic environment wherein it could be built. Wherein it could succeed! These systems idealized in lamentations at SPLASH all seem to me to be products of economic surplus combined with the right people. We need an economy that has surpluses around for people to do explorations. &lt;/p&gt;
&lt;p&gt;To be clear: I actually don’t really agree with a lot of the lamentation the Smalltalk &amp;amp; Lisp people feel. I think many of them undervalue the world we actually have. Nevertheless, I think we are missing exploration today. The ability to build systems, and sit with them, and let them evolve for years. &lt;/p&gt;
&lt;p&gt;We need surplus projects; we need the things built when people have the freedom to play, the freedom to create. Some people, unbound from the strictures of having to work themselves to the bone to eat, will produce plays, will &lt;a href="https://en.wikipedia.org/wiki/Philip_Glass#:~:text=Apart%20from%20his,staring%20at%20him"&gt;produce symphonies&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;What about open-source? Open Source can unlock some surpluses, it’s true! But nor is it without &lt;a href="https://xkcd.com/2347/"&gt;its well known problems&lt;/a&gt;. &lt;/p&gt;
&lt;p&gt;Maybe, if we can build a more just future, we’ll have SPLASH without so much lamentation. &lt;/p&gt;</description><author>Matthew Gaudet</author><pubDate>Tue, 29 Oct 2024 04:38:23 GMT</pubDate><guid isPermaLink="true">https://www.mgaudet.ca/blog/2024/10/28/before-you-can-have-smalltalk-you-must-first-defeat-capitalism</guid></item><item><title>The Tag That Shall Not Be Named</title><link>https://river.me/blog/the-tag-that-shall-not-be-named/</link><description>A quick post about transclusion, partial transclusion, The Tag That Shall Not Be Named, and whitespace</description><author>River Writes - A MediaWiki Blog</author><pubDate>Tue, 29 Oct 2024 03:31:04 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/the-tag-that-shall-not-be-named/</guid></item><item><title>Rob the Whole World; Give It Back</title><link>https://taylor.town/rob-the-world</link><description>Give a man a gun and he can rob a bank. Give a man a bank and he can rob the whole world.</description><author>taylor.town</author><pubDate>Tue, 29 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/rob-the-world</guid></item><item><title>A guide to CRT photography</title><link>https://nyanpasu64.gitlab.io/blog/crt-photography/</link><description>&lt;p&gt;It's well-documented that acquiring a CRT display instantly awakens an instinct to show off photos of game title screens and calibration patterns to every Discord friend and server within earshot, at the expense of actually playing games on the display. CRT photography is a tricky art, requiring you to account for factors like flicker, moire, brightness and color accuracy, and room glare to produce optimal results. Here is a guide to creating optimally clear photographs to share on modern monitors.&lt;/p&gt;</description><author>nyanpasu64's blog</author><pubDate>Tue, 29 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://nyanpasu64.gitlab.io/blog/crt-photography/</guid></item><item><title>School is not Enough</title><link>https://lagomor.ph/indieweb/20241028/2024-10-28/</link><description/><author>Home on Lagomorph</author><pubDate>Mon, 28 Oct 2024 20:59:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/indieweb/20241028/2024-10-28/</guid></item><item><title>A New Look</title><link>https://ztoz.blog/posts/a-new-look/</link><description>&lt;p&gt;I&amp;rsquo;ve given this blog a new look by switching the theme from &lt;a href="https://github.com/kimcc/hugo-theme-noteworthy"&gt;Noteworthy&lt;/a&gt; to &lt;a href="https://gitlab.com/jeffrey_starr/longform-hugo"&gt;Long Form&lt;/a&gt;, the latter a theme of my own design.&lt;/p&gt;
&lt;h2 id="why-the-change"&gt;Why the change?&lt;/h2&gt;
&lt;p&gt;I adopted the Noteworthy theme at the onset of this blog and maintained a branch with some fixes (primarily KaTeX support). Since it is a cliche that engineering blogs will write more about the blog&amp;rsquo;s infrastructure than actual content, I wanted to first establish a track record of content before spending too much effort on design. Now, with a few years of posts, there were a few factors that pushed me to create something new:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Printing Support&lt;/em&gt; The old site created near unprintable pages. I want my works to be useful offline.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Dense Tables&lt;/em&gt; Many of my articles feature tables, often with dense content - many columns and rows. Noteworthy&amp;rsquo;s table CSS did not scale to that level of data which hampered my ability to present information. (I expect I&amp;rsquo;ll continue to tinker with Long Form&amp;rsquo;s table formatting.)&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Unsupported&lt;/em&gt; Noteworthy was no longer supported or updated by the author. As open-source, I could have continued to maintain my branch and make other changes, but a clean slate both allows a new vision and reduces the maintenance burden.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="on-dropping-tags"&gt;On dropping tags&lt;/h2&gt;
&lt;p&gt;The previous site had a section for tags, which never worked because I hadn&amp;rsquo;t tagged any pages. I explored adopting an existing system like ACM&amp;rsquo;s &lt;a href="https://dl.acm.org/ccs"&gt;CCS&lt;/a&gt;, but that system is not supportable within Hugo&amp;rsquo;s functionality. Further, my content often does not align with CCS. There is enough content that adopting some tag or taxonomy makes sense, but I&amp;rsquo;ll wait until I have a system before advertising its presence again.&lt;/p&gt;</description><author>ℤ→ℤ</author><pubDate>Mon, 28 Oct 2024 18:17:21 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/a-new-look/</guid></item><item><title>Will we care about frameworks in the future?</title><link>https://paul.kinlan.me/will-we-care-about-frameworks-in-the-future/</link><description>Building apps with LLMs and agents like Replit has been incredibly productive. The generated code is often vanilla and repetitive, raising questions about the future of frameworks. While frameworks offer abstractions and accelerate development, LLMs seem to disregard these patterns, focusing on implementation. This shift in software development driven by agents may lead to a world where direct code manipulation is unnecessary.  It remains to be seen if frameworks and existing architectural patterns will still be relevant in this LLM-driven future or if new patterns will emerge.</description><author>Modern Web Development with Chrome</author><pubDate>Mon, 28 Oct 2024 16:54:00 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/will-we-care-about-frameworks-in-the-future/</guid></item><item><title>Welcome to the Bikeshed</title><link>https://julianwachholz.dev/bikeshed/</link><description>&lt;p&gt;Last year I set myself a goal to write a few articles on what you could call my blog. I managed to create four small posts and that was it.&lt;/p&gt;
&lt;p&gt;So I finally gave in to my true passion: writing code. I created my own tiny blogging system from scratch. You know how every dev writes their own blog at least once in life? I'm planning to document my journey here and there and hopefully give some helpful tips and tricks to people along the way. Me writing this in the already built project means this will be done retroactively but this shouldn't detract from the learning experience for you and me.&lt;/p&gt;
&lt;p&gt;In the beginning, I created a small Django project from scratch. It's not much but I wanted to incorporate every tiny little trick I've learned in the past decade or so. This comes natural to me as a full-time Django developer and long time advocate of simpler backends.&lt;/p&gt;
&lt;h3 id="features-and-performance"&gt;Features and Performance &lt;a class="anchor" href="#features-and-performance"&gt;#&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Naturally, it wouldn't be true bikeshedding with the obligatory dash of premature optimization. From the start, we're going to implement every whimsical feature I want to and also tune the system for the most possible &lt;abbr title="requests per second"&gt;RPS&lt;/abbr&gt; my little virtual server has to spare.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Going Async&lt;/strong&gt;: While Django has supported asynchronous features since 3.0 released in December 2019, it only really becamse feasible after async support was added to the ORM as well. Basic async database access was added in 4.1 and today we can write entire blogs using only &lt;code&gt;async def&lt;/code&gt;!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SQLite&lt;/strong&gt;: Network latency is the one true killer of app speed. Let's skip it entirely by using a small file living right next to our application on the same filesystem.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Caching&lt;/strong&gt;: We'll incorporate an &lt;a href="https://macarthur.me/posts/more-aggressive-cache-headers/"&gt;aggressive caching strategy&lt;/a&gt;. Not only are we practically caching to our static assets forever, we'll also use the raw &lt;a href="https://docs.djangoproject.com/en/5.0/topics/cache/#the-per-view-cache"&gt;&lt;code&gt;@cache_page&lt;/code&gt;&lt;/a&gt; decorator on our views.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Frontend assets&lt;/strong&gt;: We can minimize and optimize our JavaScripts and stylesheets and even the font you're reading this on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Making it whimsical&lt;/strong&gt;: There's no fun in today's corporate web. I want to make a website that may look a bit boring (because I'm no designer) but sports some fun features that invite you to try them out. :)&lt;/li&gt;
&lt;li&gt;And a lot more small details...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Stay tuned on future posts explaining individual aspects of this project.&lt;/p&gt;</description><author>Julian Wachholz</author><pubDate>Mon, 28 Oct 2024 14:59:05 GMT</pubDate><guid isPermaLink="true">https://julianwachholz.dev/bikeshed/</guid></item><item><title>About</title><link>https://ztoz.blog/about/</link><description>&lt;p&gt;The mapping of integers to integers is a fair summary of the work of programming. A blog about computer science, history of computing, engineering, game theory, and other things that attract my interest.&lt;/p&gt;
&lt;h2 id="contact--social-links"&gt;Contact / Social Links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="mailto:jeffrey.w.starr@protonmail.com"&gt;E-Mail&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.linkedin.com/in/jeffrey-starr-bb6753b/"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://gitlab.com/jeffrey_starr"&gt;GitLab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jeffreystarr"&gt;GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://scholar.google.com/citations?user=rEF2wXoAAAAJ"&gt;Google Scholar&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>ℤ→ℤ</author><pubDate>Mon, 28 Oct 2024 12:58:00 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/about/</guid></item><item><title>Life of a blog - blogging in 2024 and beyond</title><link>https://www.unsungnovelty.org/posts/10/2024/life-of-a-blog-blogging-in-2024-and-beyond/</link><description>&lt;p&gt;I created &lt;a href="https://www.unsungnovelty.org/"&gt;my website&lt;/a&gt; and started blogging in 2019. The decision to create a website was inspired by 2 posts - &lt;a href="https://www.jvt.me/posts/2019/07/22/why-website/"&gt;Why I Have a Website and You Should Too by Jamie Tanna&lt;/a&gt; and &lt;a href="http://john.ankarstrom.se/html/"&gt;Writing HTML in HTML by John Ankarström&lt;/a&gt;. While I haven&amp;rsquo;t gotten to writing HTML in HTML as mentioned in John&amp;rsquo;s post, I have been thinking about incorporating the end results in Hugo. This was 5 years ago. So is bloggin dead now?&lt;/p&gt;
&lt;p&gt;People have been predicting it&amp;rsquo;s death for a while now. I came across such an article right after I started this website. As a beginner to blogs and blogging, reading it felt like I missed the train. It was very discouraging for me. But having encountered a few of them over the years, I am less worried about them now. I can say with some certainty say that &lt;strong&gt;blogs and blogging will survive&lt;/strong&gt;. In fact, I feel there has been an increase in blogs thanks to next generation SSG&amp;rsquo;s like Hugo, 11ty and others. Hosting options has never been more accessible thanks to GitLab Pages, GitHub Pages, Netlify and others.&lt;/p&gt;
&lt;h2 id="life-of-a-blog"&gt;Life of a blog&lt;/h2&gt;
&lt;p&gt;But why are we seeing that blogging is dead posts from time to time? I mean, I don&amp;rsquo;t have a clear cut answer for that. Maybe what blogging &lt;em&gt;is&lt;/em&gt; for them changed? Maybe life, their interests or any unforeseen reasons made them change? It can be anything. But that is life of a blog. They live and die just like humans. Some live for 5 years. Some for 15 and others dies in just months.&lt;/p&gt;
&lt;h2 id="the-hardest-part-of-blogging-is-to-keep-writing"&gt;The hardest part of blogging is to keep writing&lt;/h2&gt;
&lt;p&gt;Maintaining a website and to keep writing is not easy. People who want to host their own website for blogging thinks creating a website is the hardest part. But it is not. Maintaining and to keep writing is the hardest part. Cos when you start a website, you have the beginner&amp;rsquo;s excitement and motivation with you. But for most, you won&amp;rsquo;t have it once you are done with your tech project - the website. So once you are done with your hello world post, it&amp;rsquo;s like a done project for you. When I starting my blog, 70% of among the people I knew and had a website were not updated for at least a year. A lot of them had just a single post. That was the odds I started my website with. So I have kept my website setup simple for maintenance. Not writing or maintaining your website is gonna be your biggest enemy for blogging.&lt;/p&gt;
&lt;p&gt;For example, this is the largest gap I have taken since my first post in 2019 for my website. But that is alright. I don&amp;rsquo;t publish regularly. I publish when I want to and when I feel like. Sometimes that means long breaks. That is the freedom my personal website gives me. But I am also aware that people might think my blog is dead since I didn&amp;rsquo;t write since January of this year.&lt;/p&gt;
&lt;h2 id="where-do-i-find-other-personal-blogs"&gt;Where do I find other personal blogs?&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s tricky cos personal blogs and websites are very decentralised as you can imagine. Blogging and blogosphere has definitely changed as well. Some people have moved their writing to platforms like Medium, Dev.to, Bear Blog or Hashnode. Even I do. I republish (or &lt;a href="https://indieweb.org/POSSE"&gt;POSSE&lt;/a&gt;) the articles in &lt;a href="https://medium.com/@unsungnovelty"&gt;Medium&lt;/a&gt;, &lt;a href="https://unsungnovelty.bearblog.dev/"&gt;Bear Blog&lt;/a&gt;, &lt;a href="https://dev.to/unsungnovelty"&gt;Dev.to&lt;/a&gt; and &lt;a href="https://unsungnovelty.hashnode.dev/"&gt;Hashnode&lt;/a&gt;. While it is great for reaching more audience, my main interest in republishing articles is to experiment with different audience and the comment section there. It helps you see how different types of people see your articles. And it helps you interact with people directly since my website is static and doesn&amp;rsquo;t have a comment section.&lt;/p&gt;
&lt;p&gt;But if you are looking for self hosted blogs and websites, you can find a good amount of them from the below places:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://250kb.club/"&gt;https://250kb.club/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://512kb.club/"&gt;https://512kb.club/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://1mb.club/"&gt;https://1mb.club/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieblog.page/"&gt;https://indieblog.page/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.cryptic.io/"&gt;https://news.cryptic.io/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://search.marginalia.nu/"&gt;https://search.marginalia.nu/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://daverupert.com/rss-club/"&gt;https://daverupert.com/rss-club/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I have to also say that I have been enjoying &lt;a href="https://search.marginalia.nu/"&gt;Marginalia search&lt;/a&gt;. You can find a lot of new interesting blogs by searching for different topics.&lt;/p&gt;
&lt;p&gt;There are more of them with their takes and inspiring stories in the below hacker news threads:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=35164819"&gt;Ask HN: What has your personal website/blog done for you?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=22800136"&gt;Ask HN: What is your blog and why should I read it?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://news.ycombinator.com/item?id=36575081"&gt;Ask HN: Could you share your personal blog here?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And on top of this, a lot of blogs will have a &lt;a href="https://indieweb.org/blogroll"&gt;blogroll&lt;/a&gt;. Mine is &lt;a href="https://www.unsungnovelty.org/about/#blogroll"&gt;here&lt;/a&gt;. That is an interesting way to find inspirations and interests of blogs you follow.&lt;/p&gt;
&lt;h2 id="i-dont-have-a-blog-how-do-i-start-one"&gt;I don&amp;rsquo;t have a blog. How do I start one?&lt;/h2&gt;
&lt;p&gt;I would also recommend you to start writing somewhere. Bear blog, Medium or dev.to will all give you a good amount of audience. It will help you to see if blogging is indeed something you would like to do. And if you are still here, I would recommend static site generators and hosting it in a custom domain. Just like writing, you can experiment hosting part with Gitlab or Github pages. Then see if it is something you want to invest in or not. Custom domains will give you more control.&lt;/p&gt;
&lt;h2 id="blogging-starter-pack"&gt;Blogging starter pack&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Netlify&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Main reason for choosing Hugo is that it is a single package. Install the package and you don&amp;rsquo;t need any other dependencies for the website itself. Your post can just be HTML &amp;amp; CSS without having to use pip or npm. That is pretty important to me. While I currently use npm for Tailwind CSS, I am in the process of writing a css only version. The ability to do that is important to me. The Go templating language which Hugo uses gives you enough power to do loops and other things as well. Another reason is Hugo has first class support for most managed hosting solutions like Gitlab and Github pages or Netlify. It is pretty straight forward. Their documentation is alright but needs a bit of getting used to. But there are ton of articles all over the internet when you want to setup features. These are good enough reasons for me to use Hugo. But I have heard good things about 11ty, Astro and Pelican.&lt;/p&gt;
&lt;p&gt;I have been using Netlify since I started this blog. They are pretty cool. You can also use Gitlab or Github pages as well. Sourcehut does provide a hosting solution for static sites with the condition that you cannot add &lt;a href="https://srht.site/limitations"&gt;tracking scripts to the website&lt;/a&gt;. I am not sure if this includes analytics. It probably does. I haven&amp;rsquo;t got the time to investigate this which is why I haven&amp;rsquo;t moved to Sourcehut pages. I am a big fan of Sourcehut and Drew and intend to move my code there in the future. But if this is true, that kind of sucks.&lt;/p&gt;
&lt;p&gt;Analytics in itself is not bad. It helps you know where the audience are and understand your writing quality among other things. In fact, I use a client side analytics provider like &lt;a href="https://www.goatcounter.com/"&gt;goat counter&lt;/a&gt; specifically for this. You can just block my analytics script if you care about it. There are no PII collected and the analytics itself are basic. So there is no harm for anyone. But analytics like Google Analytics are bad. Punishing everyone for this seems unfair. Anyways, his house his rules.&lt;/p&gt;
&lt;h2 id="blogs-and-blogging-are-not-going-anywhere"&gt;Blogs and blogging are not going anywhere&lt;/h2&gt;
&lt;p&gt;In short, blogs and blogging are not going anywhere. There will be more posts like this which encourages you to start blogging just like posts which declares it&amp;rsquo;s death. I hope the encouraging posts inspire more people to create a blog and to start writing. And I hope that number is higher than the people who didn&amp;rsquo;t start a blog because they kept seeing it&amp;rsquo;s dead.&lt;/p&gt;
&lt;h2 id="more-reading"&gt;More reading&lt;/h2&gt;
&lt;p&gt;Cloud cannon has this wonderful series of posts which talks about static site generators and other things before and after Jekyll. I would like to think that SSG&amp;rsquo;s evolution is interlinked with blogging and personal websites in a lot of ways. You will love the series. Check it out.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloudcannon.com/blog/ssg-history-1-before-jekyll/"&gt;https://cloudcannon.com/blog/ssg-history-1-before-jekyll/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="references-and-links"&gt;References and links&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://chrismcleod.dev/blog/blogging-is-where-its-at-again/"&gt;https://chrismcleod.dev/blog/blogging-is-where-its-at-again/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ribbonfarm.com/2024/10/10/ribbonfarm-is-retiring/"&gt;https://www.ribbonfarm.com/2024/10/10/ribbonfarm-is-retiring/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>unsungNovelty</author><pubDate>Mon, 28 Oct 2024 06:44:38 GMT</pubDate><guid isPermaLink="true">https://www.unsungnovelty.org/posts/10/2024/life-of-a-blog-blogging-in-2024-and-beyond/</guid></item><item><title>A Great Comet live performance review</title><link>https://river.me/blog/natasha-pierre/</link><description>I saw Natasha, Pierre, and the Great Comet of 1812 live, and I have THOUGHTS (it was incredible!)</description><author>River Writes - A MediaWiki Blog</author><pubDate>Mon, 28 Oct 2024 05:58:50 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/natasha-pierre/</guid></item><item><title>LitCrawl, and "a Cacophony of Belonging"</title><link>https://faingezicht.com/articles/2024/10/28/litcrawl/</link><description>This past Saturday, San Francisco had one of its beloved literary events. Litcrawl is a series of readings and conversations held in bookstores, bars, and cafes in the Mission District, and is part of the annual Litquake festival. Amazingly, this year was Litcrawl's 20th anniversary, and Litquake's 25th. Having lived in the neighborhood for almost 10 years, and having attended their events many times, I was excited to be part of the celebration again. 

The first Litcrawl event I went to this year was a discussion on the _Uncanny Possibilities for AI Poetry_, led by Laird Harrison, Alicia Guo, Larry Ebert, and Halim Madi. After a short introduction from Harrison, Guo gave a presentation on the history of computational poetry. She walked us through the evolution from simple random processes like taking words out of a hat to more complex procedural generation, and touched on markov chains and LLMs before showing off some of her own work playing with form. I'd come in expecting the conversation to be solely about LLMs, but with her infinite and living poems Guo stretched the conversation in a more interesting direction. Her examples reminded us that while LLMs are great at content, they haven't quite figured out form yet.

The panelists had given out handouts with poems in them, and asked the audience to guess which ones had been written by humans, and which ones by AI. Tallying the votes, we were roughly evenly split. To everyone's surprise, two out of three were generated by AI. This was a good transition for Madi to take over, and discuss his own work. He started by explaining how he experiments with OpenAI's Playground, and followed by exposing a bot finetuned on his prior works. To showcase the differences between the two, he prompted the audience to submit our answers to a seemingly simple question _"What does the Mission mean to you?"_ in an anonymous form. He fed our answers into the first bot, and showed the results, tweaking temperature and other parameters to explain the inner workings of the models. The second bot, he explained, was trained on his own work, and he repeated the same exercise against it. The same input on different weights obviously produced different results, but that astonished some of the attendees. Less surprising to me, both bots came up with poetry-passing results regardless of what settings Madi picked. Iterating a few times, Madi noted that the bot trained on his own voice did sound like him, but insisted he would not have been able to come up with many of the lines it devised. He was particularly surprised by his bot's conclusion about The Mission invoking _a cacophony of belonging_. As the audience questioned authorship in the face of AI generated content, Madi guided us to think about the role of the artist more as a curator or sculptor, rather than a direct creator.

Wrapping up, the panel opened up to the audience, with Ebert fielding the Q&amp;amp;A. The conversation ended up opening my eyes to how much of a bubble I live in. While I wasn't expecting folks in the audience to be technical (this being a literary event) I was surprised that someone attending would not be familiar with the meaning of the _LLM_ acronym, nor that an attendee would be confused about what AI _hallucinations_ are. Perhaps the most valuable aspect of Litquake and Litcrawl is the focus on community-building, and bringing people from different worlds together. Being reminded of the gap between the tech world I inhabit and the world around me is always good. It's easy to forget that not everyone is as immersed in the same topics as I am.

As I was writing this post, my editor, Cursor, decided to autocomplete one of my sentences with the following, which seemed worth sharing:

&gt; I was also pleased to be part of the LitCrawl, which is a celebration of literature and the arts.  
&gt; I was pleased to be part of the city of San Francisco, which is a place of creativity and beauty.  
&gt; I was pleased to be part of the world of literature, which is a world of imagination and wonder.  
&gt; I was pleased to be part of the human experience, which is a journey of discovery and growth.  
&gt; I was pleased to be part of the conversation, which is a dialogue of ideas and perspectives.  
&gt; I was pleased to be part of the future, which is a world of possibilities and opportunities.  
&gt; I was pleased to be part of the present, which is a moment of connection and community.  
&gt; I was pleased to be part of the past, which is a story of memory and tradition.  
&gt; I was pleased to be part of the universe, which is a mystery of existence and meaning.  
&gt; I was pleased to be part of the cosmos, which is a dance of energy and matter.  
&gt; I was pleased to be part of the earth, which is a planet of life and beauty.  
&gt; I was pleased to be part of the stars, which are a source of light and inspiration.  
&gt; I was pleased to be part of the moon, which is a symbol of mystery and magic.  
&gt; I was pleased to be part of the sun, which is a symbol of power and warmth.  
&gt; I was pleased to be part of the sky, which is a canvas of color and light.  
&gt; I was pleased to be part of the clouds, which are a dance of water and air.  
&gt; I was pleased to be part of the rain, which is a song of life and renewal.  
&gt; I was pleased to be part of the wind, which is a breath of energy and movement.  
&gt; I was pleased to be part of the trees, which are a symbol of strength and growth.  
&gt; I was pleased to be part of the flowers, which are a symbol of beauty and fragility.  
&gt; I was pleased to be part of the animals, which are a symbol of life and diversity

I'll leave it up to you to decide if this is any good or not, and whether it is poetry. I'm still not sure.

&lt;hr /&gt;

&lt;small&gt;
&lt;em&gt;Photo: House of Breaks, by me. Previously posted on &lt;a href="/photos/2022/05/23/time-travel-variety-pack/"&gt;Time Travel Variety Pack&lt;/a&gt;.
&lt;/em&gt;&lt;/small&gt;</description><author>Avy Faingezicht</author><pubDate>Mon, 28 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://faingezicht.com/articles/2024/10/28/litcrawl/</guid></item><item><title>Understanding SQL Aggregate Functions</title><link>https://dylanpaulus.com/posts/2024/understanding-sql-aggregate-functions/</link><description>&lt;p&gt;You may have heard that "data is the new oil." By itself, data is unrefined and not valuable, but given processing and refinement, it becomes precious. We gain insights into our products, applications, and customers by exploring our data. PostgreSQL exposes aggregate functions that give us the tools to transform and process our data to provide meaning.&lt;/p&gt;
&lt;p&gt;In this article, we'll take a look at how to use SQL aggregate functions, the pitfalls, and how Timescale gives us advanced tooling to aggregate time-series data.&lt;/p&gt;</description><author>Dylan Paulus' Blog</author><pubDate>Mon, 28 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://dylanpaulus.com/posts/2024/understanding-sql-aggregate-functions/</guid></item><item><title>Understanding Docker Internals: Building a Container Runtime in Python</title><link>//muhammadraza.me/2024/building-container-runtime-python/</link><description>&lt;p&gt;I’ve been working with containers professionally for several years now, using Docker and Kubernetes daily in production environments. Like many developers, I initially treated containers as a “black box” - I knew how to use them, but didn’t really understand what was happening under the hood. It wasn’t until I needed to debug a particularly container networking issue at work that I realized I needed to understand the underlying technology better.&lt;/p&gt;

&lt;p&gt;In this post, I’ll take you on a journey to breakdown container technology by building a simple container runtime in Python. We’ll explore the Linux primitives that make containers possible and implement them step by step. By the end, you’ll understand how containers work.&lt;/p&gt;

&lt;h2 id="what-actually-is-a-container"&gt;What Actually IS a Container?&lt;/h2&gt;

&lt;p&gt;Before we start building, let’s clear up a common misconception: &lt;strong&gt;containers are NOT lightweight virtual machines&lt;/strong&gt;. This comparison, while convenient for explaining containers to newcomers, is technically misleading.&lt;/p&gt;

&lt;p&gt;A virtual machine includes an entire operating system with its own kernel. Containers, on the other hand, share the host’s kernel and use Linux features to create isolated environments. Specifically, containers are built on three main Linux primitives:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Namespaces&lt;/strong&gt; - Provide isolation (process, network, filesystem, etc.)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Control Groups (cgroups)&lt;/strong&gt; - Limit and monitor resource usage (CPU, memory, I/O)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Filesystem Isolation&lt;/strong&gt; - Use chroot/pivot_root to change the root filesystem&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you run &lt;code class="language-plaintext highlighter-rouge"&gt;docker run ubuntu bash&lt;/code&gt;, Docker is essentially:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Creating namespaces to isolate the process&lt;/li&gt;
  &lt;li&gt;Setting up cgroups to limit resources&lt;/li&gt;
  &lt;li&gt;Using an overlay filesystem to provide the Ubuntu root filesystem&lt;/li&gt;
  &lt;li&gt;Executing &lt;code class="language-plaintext highlighter-rouge"&gt;/bin/bash&lt;/code&gt; in this isolated environment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s build this ourselves to see exactly how it works.&lt;/p&gt;

&lt;h2 id="understanding-linux-namespaces"&gt;Understanding Linux Namespaces&lt;/h2&gt;

&lt;p&gt;Namespaces are a Linux kernel feature that partitions kernel resources. Different processes can have different views of the system. Linux provides several types of namespaces:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;PID Namespace&lt;/strong&gt; - Process isolation. Processes in a namespace only see processes within that namespace.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Network Namespace&lt;/strong&gt; - Network isolation. Each namespace has its own network devices, IP addresses, routing tables.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Mount Namespace&lt;/strong&gt; - Filesystem isolation. Each namespace can have its own mount points.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;UTS Namespace&lt;/strong&gt; - Hostname isolation. Each namespace can have its own hostname.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;IPC Namespace&lt;/strong&gt; - Inter-process communication isolation.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;User Namespace&lt;/strong&gt; - User and group ID isolation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start by implementing the simplest form of isolation: PID namespaces.&lt;/p&gt;

&lt;h2 id="building-our-container-runtime"&gt;Building Our Container Runtime&lt;/h2&gt;

&lt;h3 id="step-1-basic-process-isolation-with-pid-namespaces"&gt;Step 1: Basic Process Isolation with PID Namespaces&lt;/h3&gt;

&lt;p&gt;Let’s create our first container that isolates processes:&lt;/p&gt;

&lt;div class="language-python highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_in_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Run a command in an isolated PID namespace.
    This creates a new process namespace where the command
    will be PID 1 and won't see host processes.
    """&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Starting container with command: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Parent process PID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create a child process
&lt;/span&gt;    &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Child process
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Create a new PID namespace
&lt;/span&gt;            &lt;span class="c1"&gt;# CLONE_NEWPID creates a new process namespace
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unshare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWPID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Mount /proc so we can see our isolated process tree
&lt;/span&gt;            &lt;span class="c1"&gt;# Note: This requires root privileges
&lt;/span&gt;            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'mount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'-t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'proc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'proc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/proc'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Container process PID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Execute the command
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execvp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Error in container: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Parent process - wait for child to complete
&lt;/span&gt;        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Container exited"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Usage: python3 simple_container.py &amp;lt;command&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geteuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This script requires root privileges"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
    &lt;span class="n"&gt;run_in_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h4 id="testing-pid-isolation"&gt;Testing PID Isolation&lt;/h4&gt;

&lt;p&gt;Save this as &lt;code class="language-plaintext highlighter-rouge"&gt;simple_container.py&lt;/code&gt; and run it:&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 simple_container.py bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Inside the container, try running:&lt;/p&gt;
&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;ps aux  &lt;span class="c"&gt;# You'll only see processes in this namespace!&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="nv"&gt;$$&lt;/span&gt;  &lt;span class="c"&gt;# This will show PID 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is our first step towards a container - we’ve isolated the process tree!&lt;/p&gt;

&lt;h3 id="step-2-filesystem-isolation-with-chroot"&gt;Step 2: Filesystem Isolation with chroot&lt;/h3&gt;

&lt;p&gt;Now let’s add filesystem isolation. We’ll create a minimal root filesystem and use &lt;code class="language-plaintext highlighter-rouge"&gt;chroot&lt;/code&gt; to change the root directory:&lt;/p&gt;

&lt;div class="language-python highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;tempfile&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;shutil&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_rootfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Create a minimal root filesystem.
    In production, this would be container image layers.
    """&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Setting up root filesystem at &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create basic directory structure
&lt;/span&gt;    &lt;span class="n"&gt;dirs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'bin'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'lib'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'lib64'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'usr'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'proc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'sys'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'dev'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'tmp'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;dirs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Copy essential binaries (bash and ls for demo)
&lt;/span&gt;    &lt;span class="n"&gt;binaries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'/bin/bash'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/bin/ls'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/bin/ps'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;binaries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Copy required shared libraries
&lt;/span&gt;            &lt;span class="n"&gt;copy_dependencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;copy_dependencies&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Copy shared library dependencies for a binary.
    Uses ldd to find dependencies.
    """&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'ldd'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                              &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                              &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="s"&gt;'=&amp;gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'=&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;lib_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lib_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                        &lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lib_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lstrip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                        &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                            &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy2&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lib_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Warning: Could not copy dependencies for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Run a command in an isolated container with its own filesystem.
    """&lt;/span&gt;
    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Starting container with command: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Child process
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Create new namespaces
&lt;/span&gt;            &lt;span class="c1"&gt;# CLONE_NEWPID: new process namespace
&lt;/span&gt;            &lt;span class="c1"&gt;# CLONE_NEWNS: new mount namespace
&lt;/span&gt;            &lt;span class="c1"&gt;# CLONE_NEWUTS: new hostname namespace
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unshare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWPID&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWNS&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWUTS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Set hostname for this container
&lt;/span&gt;            &lt;span class="n"&gt;hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"container"&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'hostname &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Change root filesystem
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chroot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Mount /proc in the container
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/proc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'mount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'-t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'proc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'proc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/proc'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Container started with hostname: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;hostname&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Root filesystem: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Execute the command
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execvp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Error in container: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Parent process
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Container interrupted"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Container exited"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geteuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This script requires root privileges"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Usage: sudo python3 container_v2.py &amp;lt;command&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create temporary root filesystem
&lt;/span&gt;    &lt;span class="n"&gt;rootfs_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempfile&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mkdtemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'container_rootfs_'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;setup_rootfs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;
        &lt;span class="n"&gt;run_container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Cleanup
&lt;/span&gt;        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Cleaning up &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;shutil&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rmtree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rootfs_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ignore_errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now when you run this, you’ll have a container with:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Isolated process tree&lt;/li&gt;
  &lt;li&gt;Isolated filesystem&lt;/li&gt;
  &lt;li&gt;Custom hostname&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 container_v2.py bash
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Try these commands inside:&lt;/p&gt;
&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nb"&gt;hostname&lt;/span&gt;  &lt;span class="c"&gt;# Should show "container"&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; /      &lt;span class="c"&gt;# Should only see our minimal filesystem&lt;/span&gt;
ps aux    &lt;span class="c"&gt;# Only processes in this namespace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id="step-3-resource-limits-with-cgroups"&gt;Step 3: Resource Limits with cgroups&lt;/h3&gt;

&lt;p&gt;Now let’s add resource limits using cgroups (control groups). This is what prevents a container from consuming all system resources:&lt;/p&gt;

&lt;div class="language-python highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CgroupManager&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Manages cgroups v2 for resource limiting.
    Modern Linux systems use cgroups v2.
    """&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;container_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;container_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;container_id&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"/sys/fs/cgroup/container_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;container_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_limit_mb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu_shares&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""
        Create a cgroup with resource limits.

        Args:
            memory_limit_mb: Memory limit in megabytes
            cpu_shares: CPU shares (1024 = 100% of one CPU)
        """&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Create cgroup directory
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Set memory limit
&lt;/span&gt;            &lt;span class="n"&gt;memory_limit_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory_limit_mb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/memory.max"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_limit_bytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

            &lt;span class="c1"&gt;# Set CPU limit
&lt;/span&gt;            &lt;span class="c1"&gt;# cpu.max format: $MAX $PERIOD (in microseconds)
&lt;/span&gt;            &lt;span class="c1"&gt;# For example, "50000 100000" means 50% of one CPU
&lt;/span&gt;            &lt;span class="n"&gt;cpu_quota&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;cpu_shares&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/cpu.max"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cpu_quota&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; 100000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Created cgroup with limits:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"  Memory: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;memory_limit_mb&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;MB"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"  CPU: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cpu_shares&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/1024 shares"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Warning: Could not set cgroup limits: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Continuing without resource limits..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""Add a process to this cgroup."""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/cgroup.procs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Warning: Could not add process to cgroup: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""Remove the cgroup."""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rmdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Warning: Could not remove cgroup: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_container_with_limits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_mb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu_shares&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="s"&gt;"""
    Run a container with resource limits.
    """&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uuid&lt;/span&gt;

    &lt;span class="n"&gt;container_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())[:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;cgroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;CgroupManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Container ID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;container_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Create cgroup with limits
&lt;/span&gt;    &lt;span class="n"&gt;cgroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_limit_mb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;memory_mb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu_shares&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cpu_shares&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Child process
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Create namespaces
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unshare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWPID&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWUTS&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Set hostname
&lt;/span&gt;            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'hostname'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'container-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;container_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Container process started (PID: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getpid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Execute command
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execvp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Error in container: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Parent process
&lt;/span&gt;        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Add container process to cgroup
&lt;/span&gt;            &lt;span class="n"&gt;cgroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Wait for container to exit
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Container interrupted"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Cleanup cgroup
&lt;/span&gt;            &lt;span class="n"&gt;cgroup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Container exited"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geteuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"This script requires root privileges"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Usage: sudo python3 container_v3.py &amp;lt;command&amp;gt; [memory_mb] [cpu_shares]"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Example: sudo python3 container_v3.py bash 100 512"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="n"&gt;memory_mb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
    &lt;span class="n"&gt;cpu_shares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;512&lt;/span&gt;

    &lt;span class="n"&gt;run_container_with_limits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_mb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu_shares&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To test the memory limit, inside the container try:&lt;/p&gt;
&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c"&gt;# This Python one-liner will try to allocate memory until it hits the limit&lt;/span&gt;
python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"a = ['x' * 1024 * 1024 for i in range(200)]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The process should be killed when it exceeds the memory limit!&lt;/p&gt;

&lt;h3 id="step-4-complete-container-runtime"&gt;Step 4: Complete Container Runtime&lt;/h3&gt;

&lt;p&gt;Now let’s put everything together into a complete, production-like container runtime:&lt;/p&gt;

&lt;div class="language-python highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c1"&gt;#!/usr/bin/env python3
&lt;/span&gt;&lt;span class="s"&gt;"""
A minimal container runtime implementation in Python.
Demonstrates how Docker-like containers work under the hood.

Usage:
    sudo python3 container.py run &amp;lt;image_dir&amp;gt; &amp;lt;command&amp;gt;

Example:
    sudo python3 container.py run ./alpine bash
"""&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;subprocess&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;shutil&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;uuid&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;"""Represents a running container with full isolation."""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;image_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memory_mb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cpu_shares&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;())[:&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image_dir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_dir&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_mb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory_mb&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu_shares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cpu_shares&lt;/span&gt;
        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"/sys/fs/cgroup/container_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_cgroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""Create and configure cgroup for resource limits."""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Memory limit
&lt;/span&gt;            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/memory.max"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_mb&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

            &lt;span class="c1"&gt;# CPU limit
&lt;/span&gt;            &lt;span class="n"&gt;cpu_quota&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu_shares&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/cpu.max"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cpu_quota&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; 100000"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Resource limits: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory_mb&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;MB RAM, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu_shares&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/1024 CPU"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Warning: Could not set cgroups: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_network&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""
        Setup network namespace and virtual network interface.
        In a real implementation, this would create veth pairs,
        bridges, and configure iptables for NAT.
        """&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Create new network namespace
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unshare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWNET&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Bring up loopback interface
&lt;/span&gt;            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'ip'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'link'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'set'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'lo'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'up'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Network namespace created"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Warning: Could not setup network: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;setup_filesystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""Setup isolated filesystem with mount namespace."""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Ensure image directory exists
&lt;/span&gt;            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image_dir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Image directory not found: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image_dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Create mount namespace
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unshare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWNS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Remount everything private to avoid propagation
&lt;/span&gt;            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'mount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'--make-rprivate'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Change to the container's root
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chroot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image_dir&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="c1"&gt;# Mount essential filesystems
&lt;/span&gt;            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/proc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'mount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'-t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'proc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'proc'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/proc'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/sys'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'mount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'-t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'sysfs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'sys'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/sys'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/dev'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'mount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'-t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'devtmpfs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'dev'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/dev'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;makedirs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/tmp'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exist_ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'mount'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'-t'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'tmpfs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'tmpfs'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'/tmp'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                         &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Filesystem isolated (root: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image_dir&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"Failed to setup filesystem: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""Run the container."""&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Starting container..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Command: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Setup cgroup first
&lt;/span&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup_cgroup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Child process - this becomes the container
&lt;/span&gt;            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Create new namespaces
&lt;/span&gt;                &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unshare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWPID&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="c1"&gt;# Process isolation
&lt;/span&gt;                    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWNS&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;    &lt;span class="c1"&gt;# Mount isolation
&lt;/span&gt;                    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWUTS&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;   &lt;span class="c1"&gt;# Hostname isolation
&lt;/span&gt;                    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CLONE_NEWIPC&lt;/span&gt;     &lt;span class="c1"&gt;# IPC isolation
&lt;/span&gt;                &lt;span class="p"&gt;)&lt;/span&gt;

                &lt;span class="c1"&gt;# Fork again to become PID 1 in the new namespace
&lt;/span&gt;                &lt;span class="n"&gt;container_pid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;container_pid&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="c1"&gt;# Grandchild - this is PID 1 in the container
&lt;/span&gt;
                    &lt;span class="c1"&gt;# Set hostname
&lt;/span&gt;                    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s"&gt;'hostname'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'container-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                                 &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="c1"&gt;# Setup filesystem
&lt;/span&gt;                    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup_filesystem&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                    &lt;span class="c1"&gt;# Setup network
&lt;/span&gt;                    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setup_network&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

                    &lt;span class="c1"&gt;# Set environment
&lt;/span&gt;                    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'HOSTNAME'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;'container-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
                    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'PATH'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'&lt;/span&gt;

                    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Container ready!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"="&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

                    &lt;span class="c1"&gt;# Execute the command
&lt;/span&gt;                    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execvp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="c1"&gt;# Child process - wait for grandchild
&lt;/span&gt;                    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container_pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Parent process
&lt;/span&gt;            &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="c1"&gt;# Add container to cgroup
&lt;/span&gt;                &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/cgroup.procs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

                &lt;span class="c1"&gt;# Wait for container to exit
&lt;/span&gt;                &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waitpid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

            &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;KeyboardInterrupt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Interrupted"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;cleanup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="s"&gt;"""Cleanup container resources."""&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rmdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cgroup_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;pass&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s"&gt;"[&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;] Container stopped"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'A minimal container runtime'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;formatter_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RawDescriptionHelpFormatter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;epilog&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"""
Examples:
  sudo python3 container.py run /path/to/rootfs bash
  sudo python3 container.py run ./alpine sh -c "echo hello from container"
  sudo python3 container.py run ./ubuntu bash --memory 256 --cpu 512
        """&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'action'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'run'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Action to perform'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'image'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Path to root filesystem'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'command'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nargs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'+'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Command to run in container'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'--memory'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'Memory limit in MB (default: 512)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'--cpu'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;default&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                       &lt;span class="n"&gt;help&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'CPU shares (default: 1024 = 1 CPU)'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Check root
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;geteuid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Error: This program requires root privileges"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Run with: sudo python3 container.py ..."&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;'run'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;memory_mb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;cpu_shares&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cpu&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id="testing-your-container-runtime"&gt;Testing Your Container Runtime&lt;/h2&gt;

&lt;p&gt;To test this, you’ll need a root filesystem. Here’s how to create a minimal one using an existing Docker image:&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a directory for our container image&lt;/span&gt;
&lt;span class="nb"&gt;mkdir &lt;/span&gt;alpine_rootfs

&lt;span class="c"&gt;# Export an Alpine Linux filesystem (requires Docker)&lt;/span&gt;
docker &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;docker create alpine:latest&lt;span class="si"&gt;)&lt;/span&gt; | &lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; alpine_rootfs &lt;span class="nt"&gt;-xf&lt;/span&gt; -

&lt;span class="c"&gt;# Or download a minimal rootfs&lt;/span&gt;
wget https://dl-cdn.alpinelinux.org/alpine/v3.18/releases/x86_64/alpine-minirootfs-3.18.0-x86_64.tar.gz
&lt;span class="nb"&gt;mkdir &lt;/span&gt;alpine_rootfs
&lt;span class="nb"&gt;tar&lt;/span&gt; &lt;span class="nt"&gt;-xzf&lt;/span&gt; alpine-minirootfs-3.18.0-x86_64.tar.gz &lt;span class="nt"&gt;-C&lt;/span&gt; alpine_rootfs

&lt;span class="c"&gt;# Now run your container!&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;python3 container.py run alpine_rootfs sh
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Inside the container, you can verify isolation:&lt;/p&gt;

&lt;div class="language-bash highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check hostname&lt;/span&gt;
&lt;span class="nb"&gt;hostname&lt;/span&gt;  &lt;span class="c"&gt;# Should show container-&amp;lt;id&amp;gt;&lt;/span&gt;

&lt;span class="c"&gt;# Check processes (only container processes)&lt;/span&gt;
ps aux

&lt;span class="c"&gt;# Check filesystem (should only see alpine files)&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; /

&lt;span class="c"&gt;# Check resource limits&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; /sys/fs/cgroup/memory.max
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id="what-we-built-vs-what-docker-does"&gt;What We Built vs. What Docker Does&lt;/h2&gt;

&lt;p&gt;Our container runtime demonstrates the core concepts, but production container runtimes like Docker/containerd do much more:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What we built:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Process isolation (PID namespaces)&lt;/li&gt;
  &lt;li&gt;Filesystem isolation (mount namespaces + chroot)&lt;/li&gt;
  &lt;li&gt;Resource limits (cgroups v2)&lt;/li&gt;
  &lt;li&gt;Basic network isolation&lt;/li&gt;
  &lt;li&gt;Hostname isolation (UTS namespace)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What Docker adds:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Image Management&lt;/strong&gt;: Layered filesystems using overlay2/AUFS&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Image Distribution&lt;/strong&gt;: Pulling images from registries&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Advanced Networking&lt;/strong&gt;: Bridge networks, overlay networks, port mapping&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Volume Management&lt;/strong&gt;: Persistent storage with bind mounts and volumes&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Security Features&lt;/strong&gt;: seccomp profiles, AppArmor/SELinux, capability dropping&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Container Orchestration APIs&lt;/strong&gt;: REST API for managing containers&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Logging &amp;amp; Monitoring&lt;/strong&gt;: stdout/stderr capture, metrics collection&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Health Checks&lt;/strong&gt;: Container health monitoring&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Restart Policies&lt;/strong&gt;: Automatic restart on failure&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id="understanding-the-security-implications"&gt;Understanding the Security Implications&lt;/h2&gt;

&lt;p&gt;It’s crucial to understand that our simple implementation lacks many security features:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;No User Namespaces&lt;/strong&gt;: Our containers run as root. Production containers should use user namespaces to map container root to unprivileged users.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;No seccomp&lt;/strong&gt;: We don’t restrict system calls. Docker uses seccomp profiles to block dangerous syscalls.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;No Capability Dropping&lt;/strong&gt;: Our containers have all Linux capabilities. Docker drops most by default.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;No AppArmor/SELinux&lt;/strong&gt;: No mandatory access control.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These missing features are why you should never use this implementation in production!&lt;/p&gt;

&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;

&lt;p&gt;By building this container runtime, we’ve demystified how containers actually work. They’re not magic - they’re clever applications of Linux kernel features that have existed for years:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Namespaces&lt;/strong&gt; (2002-2013): Provide isolation&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;cgroups&lt;/strong&gt; (2007): Provide resource limiting&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;chroot&lt;/strong&gt; (1979!): Provides filesystem isolation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker’s innovation wasn’t inventing these technologies - it was packaging them into an easy-to-use tool with great developer experience.&lt;/p&gt;

&lt;p&gt;Understanding these fundamentals makes you a better DevOps engineer. When things go wrong in production, you’ll know where to look. When you need to optimize container performance, you’ll understand the levers you can pull.&lt;/p&gt;

&lt;h2 id="further-learning"&gt;Further Learning&lt;/h2&gt;

&lt;p&gt;If you enjoyed this deep dive, here are resources to continue learning:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Linux Namespaces&lt;/strong&gt;: &lt;code class="language-plaintext highlighter-rouge"&gt;man namespaces&lt;/code&gt;, &lt;code class="language-plaintext highlighter-rouge"&gt;man unshare&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;cgroups&lt;/strong&gt;: &lt;a href="https://www.kernel.org/doc/Documentation/cgroup-v2.txt"&gt;Kernel cgroups documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;OCI Runtime Spec&lt;/strong&gt;: The standard container runtime specification&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;runc Source Code&lt;/strong&gt;: Docker’s actual container runtime&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;LXC/LXD&lt;/strong&gt;: Linux containers project - the original container tech&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also highly recommend &lt;a href="https://app.codecrafters.io/join?via=mraza007"&gt;CodeCrafters’ “Build Your Own Docker” challenge&lt;/a&gt; - it’s an interactive way to build a container runtime with guided steps.&lt;/p&gt;

&lt;h2 id="next-steps"&gt;Next Steps&lt;/h2&gt;

&lt;p&gt;In a future post, I might explore:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Implementing container image layers with overlay filesystems&lt;/li&gt;
  &lt;li&gt;Building container networking from scratch (veth pairs, bridges, NAT)&lt;/li&gt;
  &lt;li&gt;Creating a simple container orchestrator (mini-Kubernetes)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let me know in the comments what you’d like to see next!&lt;/p&gt;

&lt;hr /&gt;

&lt;h4 id="announcements"&gt;Announcements&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;If you’re interested in more content like this, I post regularly about DevOps, Python, and systems programming. Follow me on &lt;a href="https://twitter.com/muhammad_o7"&gt;Twitter/X&lt;/a&gt; for updates.&lt;/li&gt;
  &lt;li&gt;I’m available for Python and DevOps consulting. If you need help with containerization, automation, or infrastructure, feel free to reach out via &lt;a href="mailto:muhammadraza0047@gmail.com"&gt;email&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;br /&gt;
&lt;em&gt;If you share this on X, tag me &lt;a href="https://twitter.com/muhammad_o7"&gt;@muhammad_o7&lt;/a&gt; - I’d love to see your thoughts! You can also connect with me on &lt;a href="https://www.linkedin.com/in/muhammad-raza-07/"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note: Want to be notified about posts like this? Subscribe to my RSS feed or leave your email &lt;a href="https://forms.gle/M1EK61LLCxJ3iTiD7"&gt;here&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description><author/><pubDate>Mon, 28 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">//muhammadraza.me/2024/building-container-runtime-python/</guid></item><item><title>They want your ethics for $105</title><link>https://ntietz.com/blog/ethics-105-dollars/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;If you have a blog, you've probably gotten those emails that want to "collaborate" on a guest post—which often means "let us post sketchy links for SEO purposes."
Recently, I got one which was a little different flavor than the usual spam, so I bit and replies.
The end result was at the same time fascinating, unsurprising, and deeply disappointing.
Spoiler alert: these folks have bad ethics and want to pay a paltry sum for illegal unethical behavior.
It's not even that they think we're willing to do bad things, it's that they think we'll do it &lt;em&gt;for so little&lt;/em&gt;.&lt;/p&gt;
&lt;h1 id="first-contact"&gt;First contact&lt;/h1&gt;
&lt;p&gt;My first email came from Ben, an account manager&lt;sup class="footnote-reference" id="fr-maybe-1"&gt;&lt;a href="https://ntietz.com/blog/ethics-105-dollars/#fn-maybe"&gt;[1]&lt;/a&gt;&lt;/sup&gt; at a marketing company.
It wasn't your usual spam email, though it set off some mild alarm bells.
I had no intention to &lt;em&gt;actually&lt;/em&gt; work with these folks, but I was partway through an 8 hour drive (as a passenger), so I replied—I've gotta entertain myself somehow.&lt;/p&gt;
&lt;p&gt;Ben's initial email was sparse on the details, though.
It just said it's a "paid collaboration" and didn't name the client, and the post could be about &lt;em&gt;anything&lt;/em&gt;.
So while I waited for Ben to reply, I looked at the company website again.
The sketchiest thing about it was that I couldn't &lt;em&gt;find&lt;/em&gt; any of the people, they just didn't seem to exist.&lt;/p&gt;
&lt;p&gt;Look, find me a CEO or founder that &lt;em&gt;doesn't&lt;/em&gt; have a web presence or at the very least a LinkedIn account.
Especially one in a &lt;em&gt;marketing&lt;/em&gt; field.
&lt;em&gt;That&lt;/em&gt; is unrealistic.&lt;/p&gt;
&lt;h1 id="the-details-emerge"&gt;The details emerge&lt;/h1&gt;
&lt;p&gt;It didn't take long for Ben to get back to me. Here's his email, with some cuts and without names&lt;sup class="footnote-reference" id="fr-client-1"&gt;&lt;a href="https://ntietz.com/blog/ethics-105-dollars/#fn-client"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Thanks for your reply Nicole! We don't have any expectations in terms of reach, it's just a link to any one product or storepage you like from our client's ([CLIENT]) website. Here are more details and examples for the collaboration:&lt;/p&gt;
&lt;p&gt;Our client is [CLIENT], formerly known as [SKETCHY-NAME]. [Details of deal site]. This is the site from which we'd like you to link to one relevant product or brand you currently/plan to mention on your blog.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timeline &amp;amp; Payment&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The budget is $105 USD sent to you in full via PayPal. For payment, please send a PayPal invoice to this email address [BEN'S EMAIL]. Add $4 so we can cover your fees!&lt;/li&gt;
&lt;li&gt;Within one week of payment, include the mention in a blog post that is within two weeks old, and send me the post link.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Link Inclusion&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Include &lt;em&gt;one&lt;/em&gt; permanent and do-follow link to your choice of either a store page or a product page (not the homepage).
&lt;ul&gt;
&lt;li&gt;For product page URLs, remove "?run=" and characters following it.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Make it a natural and seamless fit: without mentioning [CLIENT], add the link to your post using the specific store/brand name or product name as the anchor text.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Examples&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;[SNIP (other bloggers with posts containing these sketchy links)]&lt;/p&gt;
&lt;p&gt;Please let me know if you have any questions or ideas to run by me. I'm looking forward to your thoughts!&lt;/p&gt;
&lt;p&gt;Warmly,&lt;/p&gt;
&lt;p&gt;[BEN'S NAME]&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So this is what they want.
Not a &lt;em&gt;real&lt;/em&gt; collaboration, but just straight money-for-links.
Notably, they want a do-follow link, which means they do not want the "nofollow" or "sponsored" rel attribute on the link.
They want search engine crawlers to follow these links and treat them as organic links.&lt;/p&gt;
&lt;p&gt;They're buying higher search engine rankings.&lt;/p&gt;
&lt;p&gt;This is link spam.
It's well outlined &lt;a href="https://developers.google.com/search/docs/essentials/spam-policies#link-spam"&gt;in Google's spam policies&lt;/a&gt; and if you violate that, you could &lt;em&gt;be removed from Google search results&lt;/em&gt;.
More generally, it's bad because it deceives readers into thinking this &lt;em&gt;is&lt;/em&gt; an organic link.&lt;/p&gt;
&lt;p&gt;Perhaps most troubling, they want you to do this without mentioning the client, and presumably without mentioning that you were paid at all.
I don't know about you, but usually saying "don't mention you got paid for this" signals to me that it's probably not the right thing to do.&lt;/p&gt;
&lt;h1 id="an-honest-rewrite"&gt;An honest rewrite&lt;/h1&gt;
&lt;p&gt;I prefer that people are honest about their intentions.
It makes things much easier for everyone.
Here's what an honest version of Ben's email would have looked like:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Thanks for the reply!
We don't have any expectation about reach, because we're not advertising, we're manipulating search rankings.
Our client is a division of a big American bank, so they're used to buying influence already!&lt;/p&gt;
&lt;p&gt;We'll send you $105 through the sketchiest way possible, PayPal, to make sure you feel like you're getting scammed, because you are!&lt;/p&gt;
&lt;p&gt;To get the money, just put a link to our client's shady deal-finding site in one of your blog posts.
Make sure to &lt;em&gt;not&lt;/em&gt; disclose that it's paid, because that would make us mad, since it wouldn't manipulate search rankings very well.&lt;/p&gt;
&lt;p&gt;Here are a few links to other bloggers who've already sold their ethics for &lt;em&gt;shockingly&lt;/em&gt; low amounts!
Let me know if you have any questions!&lt;/p&gt;
&lt;p&gt;- (my name isn't actually) Ben&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We probably shouldn't expect that from someone who uses a fake company with fake employees to trick people into doing an unethical thing, but I'm getting ahead of myself.&lt;/p&gt;
&lt;h1 id="who-s-behind-this"&gt;Who's behind this?&lt;/h1&gt;
&lt;p&gt;We don't know!&lt;/p&gt;
&lt;p&gt;I tried to figure out who's behind this, not least of all to report it to the FTC.
Ultimately, I couldn't figure it out, but I got some good clues.&lt;/p&gt;
&lt;p&gt;When I look at the site for the "company" that "Ben" works for, a few things stand out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The employees listed don't exist anywhere else.
I'd mentioned this before, but that's &lt;em&gt;really&lt;/em&gt; suspicious to me when I can't find &lt;em&gt;anyone&lt;/em&gt; on the roster.
If I can't find your sales folks, I'll probably assume that they're an LLM these days anyway, but if the whole company doesn't exist, then it's probably a scam.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The employees' photos are very clearly generated from a model.
I'm not going to post them here but there are some of the standard image generation artifacts: someone's ear is missing a piece under her earring; a couple people have double sets of teeth; there's a weird thing going on with the CEO's neck; another has the classic weird psychedelic image model stuff in the corner; and yet another has an earring straight out of a computerized nightmare.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The site shows it was clearly made for some other scammy site and ripped off here.
The sitemap shows last updated times of 2023 for some things, and has someone else's name as the only author.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The privacy policy is ripped off of another site, and the robots.txt file references a different site's sitemap listed in it. Whoops!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On top of all that, it's running a version of WordPress with vulnerabilities that aren't patched, and a version of OpenSSH that has &lt;a href="https://blog.qualys.com/vulnerabilities-threat-research/2024/07/01/regresshion-remote-unauthenticated-code-execution-vulnerability-in-openssh-server"&gt;a remote code execution bug&lt;/a&gt;.
So maybe they should update their servers?&lt;/p&gt;
&lt;h1 id="our-responsibilities-when-publishing"&gt;Our responsibilities when publishing&lt;/h1&gt;
&lt;p&gt;As people who publish online, we have a responsibility to our readers and to the broader internet community we're part of.
There are lots of resources to read about these, and the &lt;a href="https://www.ftc.gov/business-guidance/resources/disclosures-101-social-media-influencers"&gt;FTC influence guidelines&lt;/a&gt; are particularly good to read in my opinion.&lt;/p&gt;
&lt;p&gt;The gist of the matter to me is that as authors, we have (at least) these responsibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Tell people if you've received payment or have any relationship with a company/product/etc. mentioned.
This will let them decide if they want to trust the content or not.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make these disclosures &lt;em&gt;obvious&lt;/em&gt; and very close to what's being disclosed, so no one can reasonably miss them.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Follow norms for link attributes, like adding &lt;code&gt;rel="nofollow"&lt;/code&gt; if a link is related to a sponsor (or is just outright &lt;em&gt;paid for&lt;/em&gt;)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Do some due diligence on potential partners before you work with them.
Make sure it's a company you'd be comfortable working with in public.
Also that they, you know, exist.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On top of this, I have a few other guidelines I like to follow for my own work.
These are very much &lt;em&gt;personal&lt;/em&gt; guidelines, and I don't think they're universal.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Only publish things you'd publish otherwise.
Other than priority, it should be exactly like a normal post: the same style, the same content, just with a prompt that was bumped up by some cash&lt;sup class="footnote-reference" id="fr-once-1"&gt;&lt;a href="https://ntietz.com/blog/ethics-105-dollars/#fn-once"&gt;[3]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Make it valuable to your readers.
Sponsored posts are ads, in many ways.
Some will skip them.
Those who don't should be able to get value out of it, whether that's knowledge or entertainment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deal in public only.
Don't hide who you deal with, and only work with people you'd be comfortable telling everyone you're working with.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you have an audience, then you have a lot of responsibility to do right by them.
There's a lot of trust placed in us, and we can do a lot of harm if we do bad things for money.&lt;/p&gt;
&lt;p&gt;I think that we should be able to make a living off our work.
I'd &lt;em&gt;love&lt;/em&gt; to make a living off writing articles about silly things I've done with software&lt;sup class="footnote-reference" id="fr-profit-1"&gt;&lt;a href="https://ntietz.com/blog/ethics-105-dollars/#fn-profit"&gt;[4]&lt;/a&gt;&lt;/sup&gt;.
But getting there has to be ethical; nothing is worth doing if you can't do it ethically.
I'm not selling links, but I &lt;em&gt;am&lt;/em&gt; gonna keep responding to some of these folks for the sheer entertainment of it on long drives.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-maybe"&gt;
&lt;p&gt;I mean, that's what the email signature and website say... &lt;a href="https://ntietz.com/blog/ethics-105-dollars/#fr-maybe-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-client"&gt;
&lt;p&gt;I've omitted both the client and company names here because I don't want to give them &lt;em&gt;any&lt;/em&gt; of the traffic they're trying to buy.
I also don't know that the company is in on it—this could be the shady dealings of a company they've contracted, or further removed. &lt;a href="https://ntietz.com/blog/ethics-105-dollars/#fr-client-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-once"&gt;
&lt;p&gt;I've only done one sponsored post.
It was a pretty good experience, despite my anxiety.
But I'm not sure I'll have the opportunity (or desire) to do another one for a while. &lt;a href="https://ntietz.com/blog/ethics-105-dollars/#fr-once-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-profit"&gt;
&lt;p&gt;I think my one sponsored post has &lt;em&gt;technically&lt;/em&gt; covered all the costs for operating my blog since its inception.
That doesn't take into account labor, so I'm still very far off of making a profit if I had to pay myself.
And to get to a real salary would take a &lt;em&gt;lot&lt;/em&gt; of sponsored posts (more than I'm willing to do) or a &lt;em&gt;lot&lt;/em&gt; of &lt;del&gt;begging for money&lt;/del&gt; Patreon memberships. &lt;a href="https://ntietz.com/blog/ethics-105-dollars/#fr-profit-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 28 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/ethics-105-dollars/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Creating a Flux Dev LORA - Full Guide</title><link>https://reticulated.net/dailyai/creating-a-flux-dev-lora-full-guide/</link><description>Complete Guide to Creating Flux LORAs. Workflows included.</description><author>Reticulated</author><pubDate>Sun, 27 Oct 2024 19:02:39 GMT</pubDate><guid isPermaLink="true">https://reticulated.net/dailyai/creating-a-flux-dev-lora-full-guide/</guid></item><item><title>📗 How Big Things Get Done, by Flyvbjerg &amp;amp; Gardner</title><link>https://ernest.oppet.it/2024/10/27/how-big-things-get-done-by-flyvbjerg-gardner/</link><description>Intro Think slow, act fast The commitment fallacy Think from right to left Pixar planning Are you experienced? So you think your project is unique? Can ignorance be your friend? A single, determined organism What&amp;#8217;s your lego Eleven heuristics for better project leadership Misc</description><author>Ernest Oppetit</author><pubDate>Sun, 27 Oct 2024 10:24:36 GMT</pubDate><guid isPermaLink="true">https://ernest.oppet.it/2024/10/27/how-big-things-get-done-by-flyvbjerg-gardner/</guid></item><item><title>WP Engine Drama</title><link>https://ishan.co/wp-engine-drama/</link><description>Some links and thoughts on WP Engine Drama going on over in WordPress community</description><author>Ishan Sharma</author><pubDate>Sun, 27 Oct 2024 03:48:59 GMT</pubDate><guid isPermaLink="true">https://ishan.co/wp-engine-drama/</guid></item><item><title>Intent</title><link>https://thoughts.greyh.at/intent/</link><description>&lt;p&gt;You have found my tiny corner of the internet!&lt;/p&gt;
&lt;p&gt;This is where I share my thoughts about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Crypto&lt;/li&gt;
&lt;li&gt;Security&lt;/li&gt;
&lt;li&gt;Infrastructure&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;Food&lt;/li&gt;
&lt;li&gt;Health&lt;/li&gt;
&lt;li&gt;Spirit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can find my short form content on &lt;a href="https://x.com/zquestz"&gt;X&lt;/a&gt; or &lt;a href="https://farcaster.xyz/quest"&gt;Farcaster&lt;/a&gt;, and my code on &lt;a href="https://github.com/zquestz"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;</description><author>Terminal Thoughts</author><pubDate>Sun, 27 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://thoughts.greyh.at/intent/</guid></item><item><title>Jackson Hole, Tetons, and Yellowstone, 2024</title><link>https://faingezicht.com/photos/2024/10/27/wyoming/</link><description>In October, I got to visit Jackson Hole, Wyoming, for a work offsite with our engineering team. I was barely over a month into &lt;a href="/articles/2024/08/21/angellist/"&gt;the new job&lt;/a&gt;, and since we're pretty distributed, it was a great way to meet many of my teammates in person for the first time. While Jackson Hole was out of the way for everyone, it turned out to be an awesome backdrop for our time together. The sessions were productive, and the team-building activities were fun.

We got to explore the Tetons and Yellowstone, which were both stunning. The trip was a great way to bond with my new colleagues and experience the beautiful national parks.</description><author>Avy Faingezicht</author><pubDate>Sun, 27 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://faingezicht.com/photos/2024/10/27/wyoming/</guid></item><item><title>Process Optimization</title><link>https://www.craigpardey.com/post/2024-10-27-process-optimization/</link><description>&lt;blockquote&gt;
&lt;p&gt;Only by increasing flow through the constraint can overall throughput be increased &amp;ndash;  Eliyahu M. Goldratt, The Goal, 1984&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Turning this around we get the corollory that optimizations made to areas &lt;em&gt;other&lt;/em&gt; than the constraint only serve to worsen the constraint.&lt;/p&gt;
&lt;p&gt;Agile practices, and particularly &lt;a href="https://scrum.org/"&gt;Scrum&lt;/a&gt; and &lt;a href="https://scaledagileframework.com/"&gt;SAFe&lt;/a&gt;, have been widely adopted in large enterprises over the last decade or so, spawning entirely new departments dedicated to locking down the process. Agile Centers of Excellence (CoEs) produce materials explicitly describing every aspect of their company&amp;rsquo;s specific flavour of Agile, and have coaches dedicated to enforcing it across the teams.&lt;/p&gt;</description><author>Craig Pardey</author><pubDate>Sun, 27 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.craigpardey.com/post/2024-10-27-process-optimization/</guid></item><item><title>What I have learned about 3D printing</title><link>https://river.me/blog/3d-printing/</link><description>I started 3D printing and I do not recommend it as a hobby</description><author>River Writes - A MediaWiki Blog</author><pubDate>Sat, 26 Oct 2024 17:22:56 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/3d-printing/</guid></item><item><title>Long Time No Post! RPi Pico, Zephyr, and Whales (oh my)</title><link>https://blog.herlein.com/post/long-time-no-post/</link><description>&lt;p&gt;A short update on my whale song project:  I fell down a few technical rabbit hole!&lt;/p&gt;</description><author>Greg Herlein</author><pubDate>Sat, 26 Oct 2024 11:00:01 GMT</pubDate><guid isPermaLink="true">https://blog.herlein.com/post/long-time-no-post/</guid></item><item><title>Gmail Labels Don't Search Well</title><link>https://er4hn.info/blog/2024.10.26-gmail-labels/</link><description>Then Life Continued</description><author>er4hn</author><pubDate>Sat, 26 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://er4hn.info/blog/2024.10.26-gmail-labels/</guid></item><item><title>Miss Sloane</title><link>https://mehulkar.com/blog/2024/10/miss-sloane?utm_source=rss</link><description>&lt;div class="letterboxd-movie-data-content"&gt;
   &lt;p&gt;&lt;img src="https://a.ltrbxd.com/resized/sm/upload/bn/du/0c/tf/lPgISoC37G3E2QEVtSuJ15oYlI-0-600-0-900-crop.jpg?v=812dc89068" /&gt;&lt;/p&gt; &lt;p&gt;Watched on Saturday October 26, 2024.&lt;/p&gt; 
  &lt;p&gt;Rated 3.5 stars.&lt;/p&gt;&lt;p&gt;
  &lt;/p&gt;&lt;div class="float-clear"&gt;&lt;/div&gt;
&lt;/div&gt;

        &lt;p&gt;Thanks for reading this post via RSS!&lt;/p&gt;</description><author>Mehul Kar's blog</author><pubDate>Sat, 26 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://mehulkar.com/blog/2024/10/miss-sloane?utm_source=rss</guid></item><item><title>Impressions on the remarkable 2, one month in</title><link>https://lengrand.fr/impressions-on-the-remarkable-2-one-month-in/</link><description>This article describes my experience with the Remarkable 2 after a month of usage. I am generally happy with it, though many things are lacking in my opinion.</description><author>Julien's DevRel corner</author><pubDate>Fri, 25 Oct 2024 19:44:02 GMT</pubDate><guid isPermaLink="true">https://lengrand.fr/impressions-on-the-remarkable-2-one-month-in/</guid></item><item><title>Algorithms for Optimization (Explained Simply): Part 2 - Line Search and the Trust Region Method</title><link>https://photonlines.substack.com/p/algorithms-for-optimization-explained-f26</link><description>A visual-focused and intuitive overview of important optimization algorithms. This blog post focuses explaining the details behind the line search and the trust region method.</description><author>Photon-Lines Substack</author><pubDate>Fri, 25 Oct 2024 18:52:50 GMT</pubDate><guid isPermaLink="true">https://photonlines.substack.com/p/algorithms-for-optimization-explained-f26</guid></item><item><title>Time tracker</title><link>https://ciesie.com/project/time_tracker/</link><description>&lt;h2 id="this-is-a-work-in-progress"&gt;THIS IS A WORK IN PROGRESS&lt;a href="#this-is-a-work-in-progress"&gt; &amp;lt;&lt;/a&gt;
  
&lt;/h2&gt;
&lt;p&gt;Below you will find log entries that describe the design/thinking process.&lt;/p&gt;
&lt;h2 id="idea"&gt;Idea&lt;a href="#idea"&gt; &amp;lt;&lt;/a&gt;
  
&lt;/h2&gt;
&lt;p&gt;The goal is to build a device that could allow me to comfortably measure time spent on a
project. I want to have a full history of the time spent on particular project. The time log
needs to be plain text, easily queried and human readable.&lt;/p&gt;
&lt;h2 id="process"&gt;Process&lt;a href="#process"&gt; &amp;lt;&lt;/a&gt;
  
&lt;/h2&gt;
&lt;div class="date-decorate" style="margin-left: auto; margin-right: auto; text-align: center; color: #d30054;"&gt;
  &lt;img src="https://ciesie.com/imgs/triangle.svg" /&gt;
  &lt;div style="margin-top: 0px;"&gt;2023-08-23&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And here we go with another attempt at prototyping this device. 3D printing is great, as long as it
works. Any 3D printer issues can derail the entire process - I&amp;rsquo;m not here to practice my 3D
printing troubleshooting skills. Another thing about 3D prints is that they&amp;rsquo;re not a good base to
further refine the printed prototype. It&amp;rsquo;s a plastic shell and there&amp;rsquo;s not much you can do to
modify its shape, without redesigning and reprinting. There&amp;rsquo;s definitely way more to prototyping
than 3D printing.&lt;/p&gt;</description><author>ciesie.com</author><pubDate>Fri, 25 Oct 2024 15:12:50 GMT</pubDate><guid isPermaLink="true">https://ciesie.com/project/time_tracker/</guid></item><item><title>Reflections on the Metastrategies Workshop</title><link>https://www.georgeyw.com/reflections-on-the-metastrategies-workshop/</link><description>A weekend of trying to be better</description><author>This Is My True Name</author><pubDate>Fri, 25 Oct 2024 04:50:00 GMT</pubDate><guid isPermaLink="true">https://www.georgeyw.com/reflections-on-the-metastrategies-workshop/</guid></item><item><title>ARBORETUM</title><link>https://www.austinatchley.xyz/posts/2024/10/arboretum/</link><description>&lt;p&gt;
 &lt;img alt="ARBORETUM" src="https://www.austinatchley.xyz/images/arboretum_album_cover.jpg" /&gt;
&lt;/p&gt;
&lt;p&gt;I am excited to announce that ARBORETUM, the second album under Post Bag Productions, is &lt;a href="https://distrokid.com/hyperfollow/postbagproductions/arboretum"&gt;now streaming on all platforms! Go check it out!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t have access to a streaming service, &lt;a href="https://youtube.com/playlist?list=OLAK5uy_l0uxepRNGocg_Y9_vg3ytqmzPRs815qsY&amp;amp;si=DAYEmeZq0FO_JMWN"&gt;you can also listen to the full album on YouTube&lt;/a&gt;:&lt;/p&gt;
&lt;div style="padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
 
 &lt;/div&gt;

&lt;p&gt;This is a project I&amp;rsquo;ve been working on for the past year along with my creative partner, &lt;a href="https://www.instagram.com/sudhabuddha/"&gt;Sudarshan&lt;/a&gt;. We perserved through several cross-country moves (from Seattle to Manhattan to Brooklyn, and from Seattle to Dallas), busy schedules, and many creative blocks to put this project out. We&amp;rsquo;re excited to share a view into our lives over the past year.&lt;/p&gt;</description><author>Austin Atchley</author><pubDate>Fri, 25 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.austinatchley.xyz/posts/2024/10/arboretum/</guid></item><item><title>2024-10-25/01</title><link>https://ho.dges.online/pictures/2024-10-25-01/</link><description/><author>ho.dges.online</author><pubDate>Fri, 25 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-10-25-01/</guid></item><item><title>2024-10-25/02</title><link>https://ho.dges.online/pictures/2024-10-25-02/</link><description/><author>ho.dges.online</author><pubDate>Fri, 25 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-10-25-02/</guid></item><item><title>TLDs with novel behavior</title><link>https://alexsci.com/blog/tlds-with-novel-behavior/</link><description>Thoughts on .internal and gTLDs</description><author>Built on Shards of Silicon: Robert Alexander's Tech Blog</author><pubDate>Fri, 25 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://alexsci.com/blog/tlds-with-novel-behavior/</guid></item><item><title>gus gorman takes on the election</title><link>https://antipaucity.com/2024/10/24/gus-gorman-takes-on-the-election/</link><description>Imagine you are Gus Gorman, and you live in the United States &amp;#8211; a country which uses the Electoral College for their national election process &amp;#8211; while each state or province may award electors based on the popular vote (either an all-or-nothing approach, or proportionally), a pure &amp;#8220;most votes&amp;#8221; count does not necessarily indicate a...&lt;p class="more-link-wrap"&gt;&lt;a class="more-link" href="https://antipaucity.com/2024/10/24/gus-gorman-takes-on-the-election/"&gt;continue&lt;span class="screen-reader-text"&gt; &amp;#8220;gus gorman takes on the election&amp;#8221;&lt;/span&gt; &amp;#187;&lt;/a&gt;&lt;/p&gt;</description><author>antipaucity</author><pubDate>Thu, 24 Oct 2024 19:34:20 GMT</pubDate><guid isPermaLink="true">https://antipaucity.com/2024/10/24/gus-gorman-takes-on-the-election/</guid></item><item><title>In defense of my technophobia</title><link>andersource.github.io/2026/03/29/andersource.github.io/2024/10/24/technophobia.html</link><description>Alright I admit it, I'm a technophobe</description><author>andersource</author><pubDate>Thu, 24 Oct 2024 15:30:00 GMT</pubDate><guid isPermaLink="true">andersource.github.io/2026/03/29/andersource.github.io/2024/10/24/technophobia.html</guid></item><item><title>Calibrating a 3D Printed Delta Arm for Screen Interaction</title><link>https://dfworks.com/blog/delta_arm/</link><description>Introduction The battle between disinformation spreaders, propagandists, and automated social media activity, and the algorithms designed to…</description><author>DFWORKS | Online Threat Mitigation</author><pubDate>Thu, 24 Oct 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://dfworks.com/blog/delta_arm/</guid></item><item><title>Develop a Recipe Generator AI App in PHP Laravel using OpenAI</title><link>https://blog.adnansiddiqi.me/develop-a-recipe-generator-ai-app-in-php-using-openai/</link><description>&lt;p&gt;This post is part of the GenAI series. Alright, so the first text-based GenAI app that I am going to build is actually an AI-based recipe generator in PHP Laravel using OpenAI APIs. The app will ask about the ingredients and cuisine type and will generate a recipe for you. If you are in a hurry, you can watch the demo of the app below: To build this app, you will need to install Laravel and generate OpenAI API keys. Development Setup To make this app work you need three things: OpenAI API key generation Laravel installation OpenAI Integration OpenAI API Key generation Go to the API Keys section of the OpenAI Platform generate a new key that starts with sk- and save it somewhere. (Image below edited via chatGPT) Laravel Installation Assuming you have composer installed, you can run the following command: composer create-project laravel/laravel recipegenerator It will create a folder with the name recipegenerator. Now, run npm install to install Node packages. If all goes well, you will see something like the output below: Let&amp;#8217;s generate HomeController: php artisan make:controller HomeController and then a view: php artisan make:view home The HomeController.php file will look like this: namespace App\Http\Controllers; use Illuminate\Http\Request; class HomeController extends Controller { public function index() { return view('home'); } } and home.blade.php file: &amp;#60;!DOCTYPE html&amp;#62; &amp;#60;html lang="en"&amp;#62; &amp;#60;head&amp;#62; &amp;#60;meta charset="UTF-8"&amp;#62; &amp;#60;meta name="viewport" content="width=device-width, initial-scale=1.0"&amp;#62; &amp;#60;meta http-equiv="X-UA-Compatible" content="ie=edge"&amp;#62; &amp;#60;title&amp;#62;AI Recipe Generator&amp;#60;/title&amp;#62; &amp;#60;link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.6.2/dist/css/bootstrap.min.css"&amp;#62; &amp;#60;/head&amp;#62; &amp;#60;body&amp;#62; &amp;#60;div class="container"&amp;#62; &amp;#60;div style="margin-top: 30px;" class="row text-center"&amp;#62; &amp;#60;div class="col-md-12"&amp;#62; &amp;#60;img width="100" height="100" src="{{url('logo.webp')}}" alt=""&amp;#62; &amp;#60;h1&amp;#62;Recipie Generator&amp;#60;/h1&amp;#62; &amp;#60;/div&amp;#62; &amp;#60;/div&amp;#62; &amp;#60;div class="row"&amp;#62; &amp;#60;div class="col-md-12"&amp;#62; &amp;#60;form action=""&amp;#62; &amp;#60;div class="form-group"&amp;#62; &amp;#60;label for="ing"&amp;#62;Ingredients:&amp;#60;/label&amp;#62; &amp;#60;input type="text" class="form-control" id="ing" placeholder="Potato, Tomato etc.."&amp;#62; &amp;#60;label for="cuisine"&amp;#62;Cuisine&amp;#60;/label&amp;#62; &amp;#60;select class="form-control" name="cuisines" id="cuisineDropdown"&amp;#62; &amp;#60;option value="italian"&amp;#62;Italian&amp;#60;/option&amp;#62; &amp;#60;option value="chinese"&amp;#62;Chinese&amp;#60;/option&amp;#62; &amp;#60;option value="mexican"&amp;#62;Mexican&amp;#60;/option&amp;#62; &amp;#60;option value="indian"&amp;#62;Indian&amp;#60;/option&amp;#62; &amp;#60;option value="japanese"&amp;#62;Japanese&amp;#60;/option&amp;#62; &amp;#60;option value="french"&amp;#62;French&amp;#60;/option&amp;#62; &amp;#60;option value="thai"&amp;#62;Thai&amp;#60;/option&amp;#62; &amp;#60;option value="greek"&amp;#62;Greek&amp;#60;/option&amp;#62; &amp;#60;option value="spanish"&amp;#62;Spanish&amp;#60;/option&amp;#62; &amp;#60;option value="middleEastern"&amp;#62;Middle Eastern&amp;#60;/option&amp;#62; &amp;#60;/select&amp;#62; &amp;#60;/div&amp;#62; &amp;#60;button type="submit" class="btn btn-danger"&amp;#62;Generate&amp;#60;/button&amp;#62; &amp;#60;/form&amp;#62; &amp;#60;/div&amp;#62; &amp;#60;/div&amp;#62; &amp;#60;/div&amp;#62; &amp;#60;/body&amp;#62; &amp;#60;/html&amp;#62; And you will see something like the following: OpenAI Integration To integrate OpenAI with your PHP application you will need to install this unofficial but awesome library. composer require openai-php/client I am going to add another route, generate, that will interact with OpenAI GPT APIs and generate a recipe. OpenAI provides an array of APIs to interact with their different services: Chat, Audio, Embeddings, Fine-tuning, Assistant, and many others. We are going to use Chat Completion API for our app. This could be called the default feature of the chatGPT which is when you ask something on GPT, it uses the chat completion APIs internally. Below is the code that is interacting with GPT: public function generate(Request $request) { $apiKey = getenv('OPENAI_KEY'); $client = \OpenAI::client($apiKey); $result = $client-&amp;#62;chat()-&amp;#62;create([ 'model' =&amp;#62; 'gpt-4o-mini', 'temperature'=&amp;#62;0.5, 'max_tokens' =&amp;#62; 150, 'messages' =&amp;#62; [ ['role' =&amp;#62; 'system', 'content' =&amp;#62; 'You are helpful assistant'], ['role' =&amp;#62; 'user', 'content' =&amp;#62; 'Hello!'], ], ]); dd($result); } I opted for the latest gpt-4o-mini model because it is cheaper, faster, and sufficient for what we are looking for. You can see the list of models here. The temperature parameter controls the randomness that causes hallucinations. The value ranges between 0 and 1. The lesser the value, the more deterministic it is. max_tokens tells about the maximum number of tokens that can be generated in the chat completion. OpenAI provides an online tokenizer tool that is quite helpful. messages field contains both prompts and instructions to generate a response. When you send a message to GPT, your role is a user. When GPT responds, its role is an assistant. You can set the tone of the response by using another role called system. In chatGPT, you set the tone by adding Custom Instructions as I have done here. When I run the above code it returns the following: OpenAI\Responses\Chat\CreateResponse {#282 ▼ // app/Http/Controllers/HomeController.php:28 +id: "chatcmpl-A6Cunhx6HVafxPgPPiZmO6zWkDKsB" +object: "chat.completion" +created: 1726042977 +model: "gpt-4o-mini-2024-07-18" +systemFingerprint: "fp_54e2f484be" +choices: array:1 [▼ 0 =&amp;#62; OpenAI\Responses\Chat \ CreateResponseChoice {#278 ▼ +index: 0 +message: OpenAI\Responses\Chat \ CreateResponseMessage {#271 ▼ +role: "assistant" +content: "Hello! How can I assist you today?" +toolCalls: [] +functionCall: null } +finishReason: "stop" } ] +usage: OpenAI\Responses\Chat \ CreateResponseUsage {#270 ▼ +promptTokens: 17 +completionTokens: 9 +totalTokens: 26 } -meta: OpenAI\Responses\Meta \ MetaInformation {#276 ▼ +requestId: "req_445c1ee052f9ef65451b450ce29e7788" +openai: OpenAI\Responses\Meta \ MetaInformationOpenAI {#267 ▶} +requestLimit: OpenAI\Responses\Meta \ MetaInformationRateLimit {#279 ▼ +limit: 10000 +remaining: 9999 +reset: "6ms" } +tokenLimit: OpenAI\Responses\Meta \ MetaInformationRateLimit {#268 ▼ +limit: 10000000 +remaining: 9999840 +reset: "0s" } } } Check the usage section: +usage: OpenAI\Responses\Chat \ CreateResponseUsage {#270 ▼ +promptTokens: 17 +completionTokens: 9 +totalTokens: 26 } It tells you about both input and output tokens. promptTokens tell the number of tokens used in the prompt and in the output. The completionTokens tells about the token count generated by OpenAI API. If you go to the tokenizer website it shows something like the below: Now I am modifying the code to incorporate the actual prompt and other settings: public function generate(Request $request) { $apiKey = getenv('OPENAI_KEY'); $ingredients = $request-&amp;#62;get("ingredients"); $cuisine = $request-&amp;#62;get("cuisine"); $prompt = "Generate a recipe based on the following information:\n Ingredients: {$ingredients}\n Cuisine: {$cuisine} "; $client = \OpenAI::client($apiKey); $result = $client-&amp;#62;chat()-&amp;#62;create([ 'model' =&amp;#62; 'gpt-4o-mini', 'temperature'=&amp;#62;0.3, 'max_tokens' =&amp;#62; 500, 'messages' =&amp;#62; [ ['role' =&amp;#62; 'system', 'content' =&amp;#62; 'You are an expert chef who has expertise in different kind of cuisines.'], ['role' =&amp;#62; 'user', 'content' =&amp;#62; $prompt], ], ]); // dd($result-&amp;#62;choices[0]-&amp;#62;message-&amp;#62;content); dd($result); } It worked, but there&amp;#8217;s an issue; it consumed more than 500 tokens to return a complete response. I tweaked the system prompt and made it like this: You are an expert chef who has expertise in different kind of cuisines. The generated Recipe should have only the following headings: Ingredients and Instructions I am now instructing it to output these two sections instead of adding extra content. I also reduced the temperature. The code now looks like the following: $result = $client-&amp;#62;chat()-&amp;#62;create([ 'model' =&amp;#62; 'gpt-4o-mini', 'temperature'=&amp;#62;0.3, 'max_tokens' =&amp;#62; 500, 'messages' =&amp;#62; [ ['role' =&amp;#62; 'system', 'content' =&amp;#62; 'You are an expert chef who has expertise in different kind of cuisines. The generated Recipe should have only the following headings: Ingredients and Instructions'], ['role' =&amp;#62; 'user', 'content' =&amp;#62; $prompt], ], ]); And now it consumed 421 tokens only. You can track the usage by visiting here. Since I want to use AJAX to make a call, I am returning the data in JSON format return response()-&amp;#62;json([ 'status' =&amp;#62; 'OK', 'message' =&amp;#62; $result-&amp;#62;choices[0]-&amp;#62;message-&amp;#62;content, ]); In Postman it generates an output like the below: All is well. Now, what is needed to retrieve this data via AJAX and display it on the page? Here&amp;#8217;s the relevant code snippet: if (ingredients!= null &amp;#38;&amp;#38; cuisine != null) { $.ajax({ url: '{{ route("generate") }}', type: 'POST', data: { _token: $('meta[name="csrf-token"]').attr('content'), // CSRF token ingredients: ingredients, cuisine: cuisine }, success: function (response) { const converter = new showdown.Converter() var markdownText = response.message const html = converter.makeHtml(markdownText); $('#recipe').html(html) $('#wait').addClass('d-none') }, error: function (xhr, status, error) { // Handle errors console.log(xhr.responseText); $('#wait').addClass('d-none') $('#recipeResult').html('&amp;#60;p&amp;#62;Error: ' + xhr.responseText + '&amp;#60;/p&amp;#62;'); } }); } It generated the response in Markdown so I used a library to convert it into HTML format. Hmm, it looks good, but there&amp;#8217;s an issue; there&amp;#8217;s no indication of what the recipe name is. I converted the system prompt to the following: You are an expert chef who has expertise in different kind of cuisines. The generated Recipe should have only the following headings: Recipe name, Ingredients and Instructions And now it generates like the below: Looks cool and yummlicious, No? Conclusion So as you saw, by adding a few lines you can make your app an AI app, an AI wrapper app, that leverages LLM APIs to produce useful apps. Like always, the code is available on Github. Like always, the code is available on Github. If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/develop-a-recipe-generator-ai-app-in-php-using-openai/"&gt;Develop a Recipe Generator AI App in PHP Laravel using OpenAI&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Thu, 24 Oct 2024 13:54:46 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/develop-a-recipe-generator-ai-app-in-php-using-openai/</guid></item><item><title>Apple Magic Trackpad with swollen battery</title><link>https://b.yuxuan.org/apple-magic-trackpad-battery</link><description>&lt;p&gt;I don&amp;rsquo;t know about others, but if given a choice, I always prefer trackpads over regular mice (except for the cases of playing games, but the last game I played requiring a mouse was StarCraft 2 more than 10 years ago). Sometimes I don&amp;rsquo;t have a choice, for example when I go to the office occasionally after the pandemic, we are provided with an external display, a keyboard, and a mouse. The mouse is Logi&amp;rsquo;s MX Master, which is a nice mouse, but this scenario happens several times a day when I&amp;rsquo;m in the office:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I was scrolling down with the mouse&amp;rsquo;s scrolling wheel on a page to look for something towards the bottom of the page.&lt;/li&gt;
&lt;li&gt;I found what I was looking for and was moving on, but the scrolling wheel on the mouse keeps scrolling with its inertia energy.&lt;/li&gt;
&lt;li&gt;By moving on I usually start to press the &lt;code&gt;Control&lt;/code&gt; key, most of the time it&amp;rsquo;s for &lt;code&gt;Control-T&lt;/code&gt; to open a new tab.&lt;/li&gt;
&lt;li&gt;In chrome &lt;code&gt;Control-Scroll down&lt;/code&gt; means zoom out, so the page is zoomed out to the minimum.&lt;/li&gt;
&lt;li&gt;I start to curse and reset the zoom of that page in Chrome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I&amp;rsquo;m using a laptop, it usually comes with a trackpad so I&amp;rsquo;m good (unless it&amp;rsquo;s some old ThinkPad with a tracepoint, but I&amp;rsquo;m also ok with tracepoints, despite that I never used them for extended period). When I need to plug my laptop into an external display, I would need an external trackpad. Here comes the problem: no one sells an external trackpad except Apple.&lt;/p&gt;

&lt;p&gt;From what I can tell, Brydge had a plan to make an external trackpad, but that plan seems to be failed and it never hit the market; Logi had an external trackpad product for a while, but it&amp;rsquo;s discontinued now; Logi currently does have an external trackpad, but they don&amp;rsquo;t sell it by itself, it&amp;rsquo;s only sold as part of the &amp;ldquo;Casa Pop-up Desk&amp;rdquo; bundle.&lt;/p&gt;

&lt;p&gt;Which is why I was using 2 Apple Magic Trackpads (I think they are both &amp;ldquo;Apple Magic Trackpad 2&amp;rdquo;) at home.&lt;/p&gt;

&lt;p&gt;I don&amp;rsquo;t use any Macs any more. Those trackpads are one of the 2 apple products I&amp;rsquo;m still using (the other is their thunderbolt cable). They are plugged via USB-A - Lightning (sigh) cable to an USB hub that also connects to an external display (for one of the cases the display itself is the USB hub), because I don&amp;rsquo;t want to deal with the switching of the bluetooth when I plug different computers into the hubs.&lt;/p&gt;

&lt;p&gt;Probably because they are plugged in &lt;sup&gt;24&lt;/sup&gt;&amp;frasl;&lt;sub&gt;7&lt;/sub&gt;, I recently started to notice that one of them, the one I use with my laptops every day, no longer sits flat on the table. It starts to wobble, and the cause of that is most likely that the battery is swollen. The other seems to also be &lt;em&gt;slightly&lt;/em&gt; swollen, but far from as severe as this one.&lt;/p&gt;

&lt;p&gt;Swollen batteries are a dangerous thing, they are fire hazards. A few years ago when my Pixel Slate&amp;rsquo;s battery started to swell, I contacted Google support, and they replaced it for me for free even though it was already out of warranty. So I brought the 2 trackpads to a local Apple store yesterday.&lt;/p&gt;

&lt;p&gt;The conversation was like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Do you have the serial number of the Mac it connects to?&lt;/li&gt;
&lt;li&gt;I don&amp;rsquo;t use it with a Mac.&lt;/li&gt;
&lt;li&gt;Do you have an order number or something?&lt;/li&gt;
&lt;li&gt;(I searched my Gmail for &amp;ldquo;apple order trackpad&amp;rdquo; and found the order email for one of them from 9 years ago)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But apparently the order number from 9 years ago is no longer in their system. The staff then brought his own Mac, plugged the trackpad into it to get its serial number, and the serial number is no longer in their system either. He then tried the other one, this time I think he found the serial number in their system, but to no one&amp;rsquo;s surprise it&amp;rsquo;s also out of warranty (I later found the order email for the other one, that one was bought from BestBuy 4 years ago).&lt;/p&gt;

&lt;p&gt;After talking to his colleagues, he came back with the offer: They can replace it for me, but since it&amp;rsquo;s out of warranty, they&amp;rsquo;ll charge me $109. I then looked up how much is a new &amp;ldquo;Apple Magic Trackpad 2&amp;rdquo;. It&amp;rsquo;s $129. So they only give me $20 discount for it.&lt;/p&gt;

&lt;p&gt;I asked him to recycle the severely swollen one for me, and took the other one back home.&lt;/p&gt;

&lt;p&gt;I actually happened to see the news about &lt;a href="https://ploopy.co/trackpad/"&gt;Ploopy&amp;rsquo;s new trackpad&lt;/a&gt;, and placed a pre-order, but my order will take months to arrive. So in the meantime I&amp;rsquo;ll have to share the single trackpad between my 2 external displays, or find some spare mouse to get by. But I&amp;rsquo;m excited that I finally have an option besides Apple, and this one does not have a battery so it won&amp;rsquo;t swell and becomes a fire hazard. If this one turns out to be good, I&amp;rsquo;ll certainly buy another one to replace my other Apple trackpad.&lt;/p&gt;</description><author>La Vita è Bear</author><pubDate>Thu, 24 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://b.yuxuan.org/apple-magic-trackpad-battery</guid></item><item><title>State of Data Engineering Q3 2024</title><link>https://www.dquach.com/2024/10/23/state-of-data-engineering-q3-2024/</link><description>Here is this quarter&amp;#8217;s state of data engineering newsletter. There is only a little chat about AI this time, and a focus on Open Table Formats, the Apache Iceberg Rest Spec, Open Table Format updates, and new updates in the Amazon Data Engineering ecosystem. Prompt Engineering &amp;#8211; Meta Analysis Whitepaper One of my favorite AI [&amp;#8230;]</description><author>Dan Quach Blog</author><pubDate>Thu, 24 Oct 2024 01:21:08 GMT</pubDate><guid isPermaLink="true">https://www.dquach.com/2024/10/23/state-of-data-engineering-q3-2024/</guid></item><item><title>Big Data Processing - Basics : Part 1</title><link>https://hiteshyadav.substack.com/p/big-data-processing-basics-part-1</link><description>A small series for understanding and optimizing Spark and other data processing engines.</description><author>Hitesh Writes</author><pubDate>Wed, 23 Oct 2024 18:41:03 GMT</pubDate><guid isPermaLink="true">https://hiteshyadav.substack.com/p/big-data-processing-basics-part-1</guid></item><item><title>The Power of Analogies</title><link>https://olshansky.info/posts/the-power-of-analogies/</link><description>7 Simple analogies explaining what the Web3 protocol we work on does</description><author>🦉 olshansky 🦁</author><pubDate>Wed, 23 Oct 2024 17:35:36 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/the-power-of-analogies/</guid></item><item><title>Transatlantic: Season 1</title><link>https://olshansky.info/tv/transatlantic_season_1/</link><description>Olshansky's review of Transatlantic: Season 1</description><author>🦉 olshansky 🦁</author><pubDate>Wed, 23 Oct 2024 15:16:57 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/transatlantic_season_1/</guid></item><item><title>An AI Horror Story</title><link>https://lagomor.ph/2024/10/an-ai-horror-story/</link><description>&lt;p&gt;So I&amp;rsquo;m reading the &lt;a href="https://www.reddit.com/r/teachers/" rel="noopener noreferrer" target="_blank"&gt;teachers subreddit&lt;/a&gt; for my daily dose of misery and sense of doom about the future when I come across an unusually worded comment.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As an educator, I&amp;rsquo;ve got to agree - while crystals and essential oils can be lovely, they won&amp;rsquo;t replace a solid IEP or therapy. Let&amp;rsquo;s save the throwing for the baseball field, not the classroom!&lt;/p&gt;
&lt;p&gt;~ /u/mohsinali- (343 Karma pre Suspension)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This comment reeked of ChatGPT. This is exactly the way an un-tuned AI writes, so I follow the rabbit hole downward to see what this is all about.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Wed, 23 Oct 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/2024/10/an-ai-horror-story/</guid></item><item><title>Vendor Lock-in is an Imaginary Problem</title><link>/vendor-lockin/</link><description>&lt;p&gt;Vendor lock-in is often cited as a reason to chose on-premise hosting, or to be mindful of &amp;quot;avoiding&amp;quot; it when using the cloud (whatever that means). Whilst there are some compelling arguments to mitigate risks associated with vendor lock-in, I'd argue that this is a completely imaginary problem.&lt;/p&gt;
&lt;h2&gt;1. You're already locked-in - at every level&lt;/h2&gt;
&lt;p&gt;Vendor lock-in is always discussed through the lens of the cloud. But, in reality the cloud is not a special class of tooling, it's like everything else. If you use a language like Javascript, you're already locked in to using the myriad of NPM packages you have. Worst still is if you're using NextJS, React, Rails or Laravel. These frameworks all lock you in to some degree. What about your continuous integration? The same organisations that worry about vendor lock-in to the cloud providers never bat an eye lid writing thousands of bespoke lines of github actions workflows.
The point is, the cloud is not the only place you're locked in. You're locked-in everywhere. But, it's about making appropriate trade-off.&lt;/p&gt;
&lt;h2&gt;2. Businesses rarely migrate from a cloud provider&lt;/h2&gt;
&lt;p&gt;Asides from a few extremely rare cases, it's likely that once AWS or whatever other cloud provider puts their claws into you, you won't escape it's clutches. Rather you'll likely embrace it. Abandoning the lift-and-shift you originally oversaw, and build &amp;quot;cloud native&amp;quot; - but why? Because it's cheaper (on the surface) and the recommended way to do things (according to the cloud providers). You also end up hiring staff with the skills to wrestle these tools to do your bidding. It's unlikely then that after doing all that work you would want to migrate to another provider. No only is the cost so astronomically high to make it unfeasible but there is also no reason, commercial or otherwise, for doing so. Even if they heavily raise prices, it's still a footnote for the costs that most businesses pay. In some cases however, at enormous scale, it does become economically advisable to migrate elsewhere. If you built the product in a vendor agnostic way, I'm sure the migration team will thank you. But if not, you're likely at a scale that the cost of doing this migration is just viewed as &amp;quot;one of those things&amp;quot;.&lt;/p&gt;
&lt;h2&gt;3. You know what you're signing up for&lt;/h2&gt;
&lt;p&gt;When you go to a shopping centre (or mall), you might need to buy some clothes, gifts or yet another Apple device. But then you suddenly realise that you need shampoo. Now, will you go to the pharmacy shop that sells the shampoo despite the cost being a little higher? Or will you get it from your regular supermarket that is 20 minutes away? Of course, you'll pay the premium and buy it at the shopping centre. There are no tangible benefits to the product - they are the same. What you are paying for is convenience.
The cloud is no different, you might go in first of all to simply get some compute. But then quickly you start using it for everything - it's convenient. The business has already signed off on it, there is budget and your tooling is setup for it.
In other words, you know what you signed up for when you started using the compute. And if you need to pay a bit extra to use other things then big deal, you would have paid that cost elsewhere in the first place.
Public cloud pricing is publicly available (although notoriously difficult to forecast). So, due diligence should be taken up front to understand what kind of cost implications the cloud could have. While convenience often outweighs cost at smaller scales (like buying shampoo one time), in larger enterprises, even slight cost increases can lead to substantial expenses. This is why some companies consider multi-cloud or cloud-agnostic strategies to balance convenience with financial prudence.&lt;/p&gt;
&lt;h2&gt;The only sound argument against vendor lock-in&lt;/h2&gt;
&lt;p&gt;Local development. That's it. Once you use bespoke cloud software, it becomes extremely challenging to create working development environments that mimic your live deployment. Therefore, I do favour against using &amp;quot;cloud-native&amp;quot; tools because it makes your life a lot more difficult for doing development work (which makes the money).&lt;/p&gt;
&lt;h2&gt;Summary&lt;/h2&gt;
&lt;p&gt;As discussed vendor lock-in is largely an imaginary problem. There may well be 1 (or 2) extremely valid reasons for avoiding vendor lock-in but generally it's something that you need to not worry about and get on with shipping.&lt;/p&gt;</description><author/><pubDate>Wed, 23 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">/vendor-lockin/</guid></item><item><title>Gympl skripta</title><link>https://chamik.eu/gympl-skripta/</link><description>Zápisky ze školní češtiny a angličtiny k maturitě na GJP-ME.</description><author>Kubíkovo</author><pubDate>Wed, 23 Oct 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://chamik.eu/gympl-skripta/</guid></item><item><title>7 Pocket Network Analogies</title><link>https://olshansky.info/posts/7-pocket-network-analogies/</link><description>tl;dr Explaining what Pocket Network is through a series of 7 internet networking analogies. Subscribe if you want to be notified of when…</description><author>🦉 olshansky 🦁</author><pubDate>Wed, 23 Oct 2024 00:40:47 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/7-pocket-network-analogies/</guid></item><item><title>Blog update pains</title><link>https://callmeo.live/blog/blog-update-pains/</link><description>&lt;blockquote&gt;
&lt;p&gt;This blog post will not be updated to match the site in the future, any mention of how things are will remain that way.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Firstly, I made this blog with two key influences in mind:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Herman Martinus&lt;/strong&gt;&amp;rsquo; &lt;a href="https://bearblog.dev/"&gt;Bear Blog&lt;/a&gt; for it&amp;rsquo;s simple &amp;amp; lightweight ethos&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;InvisibleUp&lt;/strong&gt;&amp;rsquo;s &lt;a href="https://invisibleup.com/articles/"&gt;unique article banners&lt;/a&gt; featured with each post&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I mentioned this prior in a now-deleted blog post. With that post now gone, I feel it&amp;rsquo;s best to publicly credit my influences again and this is the best post to do it in.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I made this blog is because &lt;a href="https://en.wikipedia.org/wiki/Enshittification"&gt;Enshittification&lt;/a&gt; is ruining all the platforms out there. The only way to escape it is to have my own place online, free from the issues these platforms constantly suffer from.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Now there&amp;rsquo;s a catch, I&amp;rsquo;m not a genius. My only experience of webdev was using Dreamweaver CS3 back in school. I knew about Static Site Generators, but I couldn&amp;rsquo;t wrap my head around them. Because of this, I always updated my blog manually with HTML like it&amp;rsquo;s still 1999, which was a nightmare.&lt;/p&gt;
&lt;p&gt;Writing a blog post was easy, but getting it online was such an arduous task that it completely put me off writing. Something had to change. I needed to sit down and learn a Static Site Generator, or suffer.&lt;/p&gt;
&lt;div class="centre"&gt;&lt;p&gt;~&lt;/p&gt;&lt;/div&gt;

&lt;p&gt;I chose &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt; for one reason: everyone said it was simple and easy to learn. This was a lie. Hugo is hell to learn for the following reasons:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Documentation exists, but it&amp;rsquo;s nigh-incomprehensible for a newbie to digest.&lt;/li&gt;
&lt;li&gt;Hugo is &lt;em&gt;too&lt;/em&gt; versatile – there is no one way to do a task. This leads to each theme accomplishing the same thing with different methods, which doesn&amp;rsquo;t help new users figure out the best practices&lt;/li&gt;
&lt;li&gt;90% of the help out there was on closed forum posts, with the only reply being from a moderator pointing someone to the documentation with no actual explanation&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don&amp;rsquo;t want to sound like I&amp;rsquo;m slagging off the people behind Hugo, it&amp;rsquo;s a great tool but as someone new to SSGs as a whole it&amp;rsquo;s practically impossible to understand. Every time I tried learning how Hugo worked I&amp;rsquo;d end up with a debilitating stress headache and exhaust myself from failure. Each time I&amp;rsquo;d quit on the verge of tears, time and time again.&lt;/p&gt;
&lt;p&gt;Zachary Betz has a &lt;a href="https://zwbetz.com/make-a-hugo-blog-from-scratch/"&gt;clear tutorial&lt;/a&gt; for making a Hugo blog from scratch, it&amp;rsquo;s solid, but not for me. I&amp;rsquo;ve nothing against the tutorial, but I struggle with &lt;em&gt;bottom-up&lt;/em&gt; learning with anything hands-on. If you asked me to build a house, I&amp;rsquo;d learn more from taking one apart first.&lt;/p&gt;
&lt;p&gt;What helped me the most was Jan Raasch&amp;rsquo;s &lt;a href="https://janraasch.github.io/hugo-bearblog/"&gt;&lt;em&gt;Hugo Bear Blog&lt;/em&gt;&lt;/a&gt; theme – which cleanly recreates the look of Bear Blog. I meticulously dissected it to see what each building block did. At 2am on Sunday, neck deep in the partial HTML templates, something clicked.&lt;/p&gt;
&lt;div class="centre"&gt;&lt;p&gt;&lt;strong&gt;I became &lt;a href="https://youtu.be/H-0RHqDWcJE?t=156"&gt;The One&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;&lt;/div&gt;

&lt;p&gt;Even if I couldn&amp;rsquo;t explain with words or even comprehend it, I had an innate understanding of how every piece fit together, and I had the power to rewrite everything as I saw fit. There&amp;rsquo;s no more liberating feeling than the fire of discovery. In a state of pure flow I had to pry myself away from the laptop at 6am lest I wouldn&amp;rsquo;t sleep at all. All I wanted to do was scream at the world that I finally got it.&lt;/p&gt;
&lt;div class="centre"&gt;&lt;p&gt;~&lt;/p&gt;&lt;/div&gt;

&lt;p&gt;Upon waking I spent the entirety the day recreating my site in Hugo, setting it up in a way that all I&amp;rsquo;d have to do to make a blog post would be to write a Markdown file and make a banner image. If I wanted to change the core layout of the page, all I have to do is change a single HTML file. I can&amp;rsquo;t state how much time this will save in the future.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;On top of that I made a few changes to how the site looks to [hopefully] make it cleaner and easier to read. I&amp;rsquo;ve ditched Open Sans for Anek Latin, we have tags now, and the site should be readable for devices which don&amp;rsquo;t support WebP. It&amp;rsquo;s not perfect, but it&amp;rsquo;s good enough for now.&lt;/p&gt;
&lt;p&gt;But the pain of repeated failure, the countless headaches trying to understand how Hugo works. It&amp;rsquo;s finally worth it, and I think I&amp;rsquo;m just going to collapse in bed now.&lt;/p&gt;
&lt;p&gt;Again I have to thank &lt;a href="https://www.janraasch.com/"&gt;Jaan Raasch&lt;/a&gt;, whose Hugo theme was easy to understand. And I&amp;rsquo;ll apologise to him for absolutely butchering it to create this.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Namely login barriers, obnoxious cookie + mailing list banners, absurd page bloat, and advertisements. They&amp;rsquo;re all intrusive and make an otherwise  decent reading experience a nightmare.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;I can. I made this footnote instantly. Also not having to faff about with the timecodes on the RSS feed is a lifesaver.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>callmeolive</author><pubDate>Wed, 23 Oct 2024 00:00:00 GMT</pubDate><guid isPermaLink="true">https://callmeo.live/blog/blog-update-pains/</guid></item><item><title>ferris sweep split keyboard build</title><link>https://evilcookie.de/ferris-sweep-split-keyboard-build.html</link><description/><author>blog</author><pubDate>Tue, 22 Oct 2024 21:09:42 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/ferris-sweep-split-keyboard-build.html</guid></item><item><title>Event handing in Swift</title><link>https://june.kim/event-handling-in-swift/</link><author>june.kim</author><pubDate>Tue, 22 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://june.kim/event-handling-in-swift/</guid></item><item><title>Language model random number generator</title><link>https://www.danielcorin.com/posts/2024/llm-rand/</link><description>Language model random number generator</description><author>Thought Eddies</author><pubDate>Tue, 22 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2024/llm-rand/</guid></item><item><title>2024-10-22</title><link>https://ho.dges.online/pictures/2024-10-22/</link><description/><author>ho.dges.online</author><pubDate>Tue, 22 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-10-22/</guid></item><item><title>Towards the Fastest Brainf*** Implementation Ever</title><link>https://lambdaland.org/posts/2024-10-22_bf_writeup/</link><description>&lt;p&gt;In &lt;a href="https://lambdaland.org/posts/2024-09-27_threaded_interpreter/"&gt;my last post&lt;/a&gt; I described how I made a very fast BF interpreter. Well, there&amp;rsquo;s a lot more speed to be had with an optimizing compiler. This post is a write-up of my assignment for a compilers class, so the post a little rougher than normal.&lt;/p&gt;
&lt;p&gt;You can find the code at the following places:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/ashton314/brainfreeze"&gt;GitHub repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://codeberg.org/ashton314/brainfreeze"&gt;Codeberg repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="code-organization"&gt;
  Code organization
  &lt;a class="anchor" href="#code-organization"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Files for the compiler:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;&lt;code&gt;interp_threaded_opt.rkt&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;BF parser and all optimizations live here.&lt;/dd&gt;
&lt;dt&gt;&lt;code&gt;native.rkt&lt;/code&gt;&lt;/dt&gt;
&lt;dd&gt;Takes the optimized AST from &lt;code&gt;interp_threaded_opt.rkt&lt;/code&gt; and emits native ARM instructions for my M1 Pro chip.&lt;/dd&gt;
&lt;/dl&gt;
&lt;h3 id="interp-threaded-opt-dot-rkt"&gt;
  &lt;code&gt;interp_threaded_opt.rkt&lt;/code&gt;
  &lt;a class="anchor" href="#interp-threaded-opt-dot-rkt"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The first role of this module is to define an AST for BF. My AST is represented as Racket structs; these are the nodes that I have:&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Node&lt;/th&gt;
          &lt;th&gt;Fields&lt;/th&gt;
          &lt;th&gt;Comment&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;loop&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;body&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Loops hold their entire body within them. There is a parsing pass to the code that matches all &lt;code&gt;[&lt;/code&gt; &lt;code&gt;]&lt;/code&gt; characters.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;add&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;amount&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Sequences of &lt;code&gt;+&lt;/code&gt; or &lt;code&gt;-&lt;/code&gt; get smooshed together into single add instructions.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;shift&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;amount&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Like &lt;code&gt;add&lt;/code&gt;, but for &lt;code&gt;&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;&lt;/code&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;set-cell&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;value&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Set the current cell to a specific value. Used to optimize &lt;code&gt;[-]&lt;/code&gt; and the like.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;bf-write&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;/td&gt;
          &lt;td&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;bf-read&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;/td&gt;
          &lt;td&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;add-cell-0&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;dest&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Zero current cell; add old value to another cell. This is a common loop that I recognized and optimized.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;mult-block-0&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;body&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Result of optimizing simple loops. Body is a map from tape offset → value.&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;search-0&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;&lt;code&gt;stride&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;Representation of scan loops like &lt;code&gt;[&amp;lt;&amp;lt;&amp;lt;&amp;lt;]&lt;/code&gt;.&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The rest of the module is devoted to optimizations performed on the AST. Initially, only &lt;code&gt;loop&lt;/code&gt;, &lt;code&gt;add&lt;/code&gt;, &lt;code&gt;shift&lt;/code&gt;, &lt;code&gt;bf-write&lt;/code&gt;, and &lt;code&gt;bf-read&lt;/code&gt; cells are present, but through the optimization passes more get added. See &lt;a href="#optimizations"&gt;§ Optimizations&lt;/a&gt; for details on all the optimization passes.&lt;/p&gt;
&lt;p&gt;There is a &lt;code&gt;compile&lt;/code&gt; function, but this just compiles the optimized AST down to Racket closures with a threaded interpreter.&lt;/p&gt;
&lt;h3 id="native-dot-rkt"&gt;
  &lt;code&gt;native.rkt&lt;/code&gt;
  &lt;a class="anchor" href="#native-dot-rkt"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The main entry point is the &lt;code&gt;compile-to-asm&lt;/code&gt; function, which emits some fixed prelude code, the body of the program, and then some fixed postlude code.&lt;/p&gt;
&lt;p&gt;There are a bunch of functions prefixed with &lt;code&gt;i/&lt;/code&gt;; these are to help me generate assembly instructions with a particular format. Mostly just wrappers around calls to &lt;code&gt;format&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;emit-asm&lt;/code&gt; function does the hard work of walking the AST and generating instructions for each node. There&amp;rsquo;s a loose set of conventions around various registers; the most important is that &lt;code&gt;x20&lt;/code&gt; always holds the address of the start of the tape, and &lt;code&gt;x21&lt;/code&gt; holds the current pointer. I never use these registers for anything else; you can always access the current value under the program head with &lt;code&gt;[x20, x21]&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There are two special functions for emitting the assembly for &lt;code&gt;search-0&lt;/code&gt; nodes: one for forward scans (&lt;code&gt;[&amp;gt;]&lt;/code&gt;) and one for backward scans (&lt;code&gt;[&amp;lt;]&lt;/code&gt;). This is just done for organizational convenience.&lt;/p&gt;
&lt;h2 id="optimizations"&gt;
  Optimizations
  &lt;a class="anchor" href="#optimizations"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The entry point to optimizing the BF AST is in the &lt;code&gt;optimize&lt;/code&gt; function in &lt;code&gt;interp_threaded_opt.rkt&lt;/code&gt;. This chains optimizations together; each optimization function must take a whole program AST and return a whole program AST functionally. The optimizations are in order they are performed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;combine-instrs&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This pass takes sequences of instructions like &lt;code&gt;+++&lt;/code&gt;, initially represented with &lt;code&gt;(list (add 1) (add 1) (add 1))&lt;/code&gt; and turns it into &lt;code&gt;(add 3)&lt;/code&gt;. Same thing for pointer shift instructions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;opt/useless&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This cleans up instructions like &lt;code&gt;(shift 0)&lt;/code&gt; or &lt;code&gt;(add 0)&lt;/code&gt; that have no effect.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;opt/add&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This recognizes patterns like &lt;code&gt;[&amp;gt;+&amp;lt;-]&lt;/code&gt;, which adds the value of one cell to another. It handles arbitrary shift and add amounts, as long as they&amp;rsquo;re balanced. Kind of like a baby &lt;code&gt;opt/basic-loop&lt;/code&gt; pass.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;opt/zero-out&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Handles cases of &lt;code&gt;[-]&lt;/code&gt; or &lt;code&gt;[+]&lt;/code&gt; to set the cell to 0.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;opt/basic-loop&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Does the basic loop optimization we discussed in class. Easily the most complicated optimization. It abstractly evaluates the body of a loop; it sets the initial pointer to &lt;code&gt;0&lt;/code&gt; and tracks what additions go to which offsets. Bails out if anything gets too complicated.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;opt/zero-add-&amp;gt;set&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Optimizes cases of &lt;code&gt;[-]+++&lt;/code&gt; to just set the current cell value to 3.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;opt/0-scan&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Scan loops, as discussed in class.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="evaluation"&gt;
  Evaluation
  &lt;a class="anchor" href="#evaluation"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="extensive-tests--bfcheck-dot-pl"&gt;
  Extensive tests (&lt;code&gt;bfcheck.pl&lt;/code&gt;)
  &lt;a class="anchor" href="#extensive-tests--bfcheck-dot-pl"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I precompiled and ran all the files in the &lt;code&gt;check/&lt;/code&gt; folder provided. I noticed that the first run of the resulting binaries took a long time, and that they all ran almost instantly afterwards. I noticed that there was a bunch of network activity; seems like macOS was being zealous in sending signatures for &lt;em&gt;each&lt;/em&gt; of the binaries the first time they were run. The benchmarks run quickly, so this added up. The numbers reported here are &lt;em&gt;after&lt;/em&gt; running the tests one time to avoid the network penalty.&lt;/p&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Optimization set&lt;/th&gt;
          &lt;th&gt;Total time (seconds)&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;No optimizations&lt;/td&gt;
          &lt;td&gt;0.763064&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Basic loops&lt;/td&gt;
          &lt;td&gt;0.767731&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Scan loops&lt;/td&gt;
          &lt;td&gt;0.790222&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;Scan &amp;amp; basic loops&lt;/td&gt;
          &lt;td&gt;0.763272&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There&amp;rsquo;s not a whole lot of variance.&lt;/p&gt;
&lt;h3 id="benchmarks--mandel-dot-b-etc-dot"&gt;
  Benchmarks (&lt;code&gt;mandel.b&lt;/code&gt;, etc.)
  &lt;a class="anchor" href="#benchmarks--mandel-dot-b-etc-dot"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I ran my compiler on some of the benchmarks from the BF benchmarks suite we&amp;rsquo;ve been using. I also tried out a few with the &lt;code&gt;cgbfi.b&lt;/code&gt; BF-interpreter-in-BF.&lt;/p&gt;
&lt;div class="table-caption"&gt;
  &lt;span class="table-number"&gt;Table 1:&lt;/span&gt;
  Execution times for two benchmarks, natively compiled. All times in seconds.
&lt;/div&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Benchmark&lt;/th&gt;
          &lt;th&gt;No opts&lt;/th&gt;
          &lt;th&gt;Basic loops&lt;/th&gt;
          &lt;th&gt;Scan loops&lt;/th&gt;
          &lt;th&gt;Scan &amp;amp; basic loops&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;hanoi.b&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;0.11&lt;/td&gt;
          &lt;td&gt;0.05&lt;/td&gt;
          &lt;td&gt;0.11&lt;/td&gt;
          &lt;td&gt;0.05&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;mandel.b&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;0.73&lt;/td&gt;
          &lt;td&gt;0.49&lt;/td&gt;
          &lt;td&gt;0.73&lt;/td&gt;
          &lt;td&gt;0.49&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Seems that basic loop optimization is most important for the Hanoi and Mandelbrot benchmarks. I also tried the Sierpinski triangle benchmark but it never ran long enough for me to notice any difference.&lt;/p&gt;
&lt;p&gt;Contrast with benchmarks running under the &lt;code&gt;cgbfi.b&lt;/code&gt; interpreter: scan loops are far and away the most important optimization:&lt;/p&gt;
&lt;div class="table-caption"&gt;
  &lt;span class="table-number"&gt;Table 2:&lt;/span&gt;
  Execution times for benchmarks running under the &lt;code&gt;cgbfi.b&lt;/code&gt; interpreter. All times in seconds. *Gave up after 2 hours; estimated duration recorded. †No attempt.
&lt;/div&gt;
&lt;table&gt;
  &lt;thead&gt;
      &lt;tr&gt;
          &lt;th&gt;Benchmark&lt;/th&gt;
          &lt;th&gt;No opts&lt;/th&gt;
          &lt;th&gt;Basic loops&lt;/th&gt;
          &lt;th&gt;Scan loops&lt;/th&gt;
          &lt;th&gt;Scan &amp;amp; basic loops&lt;/th&gt;
      &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;serptri.b&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;0.07&lt;/td&gt;
          &lt;td&gt;0.06&lt;/td&gt;
          &lt;td&gt;0.03&lt;/td&gt;
          &lt;td&gt;0.02&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
          &lt;td&gt;&lt;code&gt;mandel.b&lt;/code&gt;&lt;/td&gt;
          &lt;td&gt;*44,052.00&lt;/td&gt;
          &lt;td&gt;†&lt;/td&gt;
          &lt;td&gt;†&lt;/td&gt;
          &lt;td&gt;7,818.96&lt;/td&gt;
      &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="example-optimizations"&gt;
  Example optimizations
  &lt;a class="anchor" href="#example-optimizations"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="basic-loops"&gt;
  Basic loops
  &lt;a class="anchor" href="#basic-loops"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;This code:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-nil"&gt;+++[-&amp;gt;+&amp;gt;-&amp;lt;&amp;lt;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Gets optimized to this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-asm"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;ldrsb&lt;/span&gt;   &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x20&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;; add 3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;add&lt;/span&gt;     &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;#3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;strb&lt;/span&gt;    &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x20&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;add&lt;/span&gt;     &lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x20&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;   &lt;span style="color: #616e87; font-style: italic;"&gt;; mult-block-0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;ldrsb&lt;/span&gt;   &lt;span style="color: #8fbcbb;"&gt;x23&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;add&lt;/span&gt;     &lt;span style="color: #8fbcbb;"&gt;x24&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;#1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;ldrsb&lt;/span&gt;   &lt;span style="color: #8fbcbb;"&gt;x25&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x24&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;mov&lt;/span&gt;     &lt;span style="color: #8fbcbb;"&gt;x11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x23&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;add&lt;/span&gt;     &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;w25&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;strb&lt;/span&gt;    &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x24&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;add&lt;/span&gt;     &lt;span style="color: #8fbcbb;"&gt;x24&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;#2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;ldrsb&lt;/span&gt;   &lt;span style="color: #8fbcbb;"&gt;x25&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x24&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;mov&lt;/span&gt;     &lt;span style="color: #8fbcbb;"&gt;x11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x23&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;subs&lt;/span&gt;    &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;w25&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;strb&lt;/span&gt;    &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x24&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #88c0d0;"&gt;strb&lt;/span&gt;    &lt;span style="color: #8fbcbb;"&gt;wzr&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x20&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Any loop with I/O or nested loops—even something like setting something to &lt;code&gt;0&lt;/code&gt; are not covered by this.&lt;/p&gt;
&lt;h3 id="scan-loops"&gt;
  Scan loops
  &lt;a class="anchor" href="#scan-loops"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;This code:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-nil"&gt;[&amp;gt;&amp;gt;&amp;gt;&amp;gt;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Gets optimized to this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-asm"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #88c0d0;"&gt;adrp&lt;/span&gt;      &lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;lIDX@PAGE&lt;/span&gt;              &lt;span style="color: #616e87; font-style: italic;"&gt;; search-0
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;ldr&lt;/span&gt;       &lt;span style="color: #8fbcbb;"&gt;q0&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;lIDX@PAGEOFF&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;     &lt;span style="color: #616e87; font-style: italic;"&gt;; v0 = idx vector
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;adrp&lt;/span&gt;      &lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;lSTRIDE4@PAGE&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #88c0d0;"&gt;ldr&lt;/span&gt;       &lt;span style="color: #8fbcbb;"&gt;q3&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;lSTRIDE4@PAGEOFF&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;; v3 = stride mask
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;movi.2d&lt;/span&gt;   &lt;span style="color: #8fbcbb;"&gt;v1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;#0                      ; v1 = zero vect
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;subs&lt;/span&gt;      &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;#16
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;LBB0_0:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #88c0d0;"&gt;add&lt;/span&gt;       &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;#16
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;add&lt;/span&gt;       &lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x20&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #88c0d0;"&gt;ld1&lt;/span&gt;       &lt;span style="color: #bf616a;"&gt;{&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;v2.16b&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;}&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;             &lt;span style="color: #616e87; font-style: italic;"&gt;; v2 = tape
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;cmeq.16b&lt;/span&gt;  &lt;span style="color: #8fbcbb;"&gt;v4&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;v2&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;v1&lt;/span&gt;                  &lt;span style="color: #616e87; font-style: italic;"&gt;; v4 = tape == zeros
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;and.16b&lt;/span&gt;   &lt;span style="color: #8fbcbb;"&gt;v4&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;v4&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;v3&lt;/span&gt;                  &lt;span style="color: #616e87; font-style: italic;"&gt;; mask with stride
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;orn.16b&lt;/span&gt;   &lt;span style="color: #8fbcbb;"&gt;v4&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;v0&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;v4&lt;/span&gt;                  &lt;span style="color: #616e87; font-style: italic;"&gt;; idx or !(tape == zeros)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;uminv.16b&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;b5&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;v4&lt;/span&gt;                      &lt;span style="color: #616e87; font-style: italic;"&gt;; find smallest idx
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;umov&lt;/span&gt;      &lt;span style="color: #8fbcbb;"&gt;w22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;v5.b&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #b48ead;"&gt;0&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #88c0d0;"&gt;subs&lt;/span&gt;      &lt;span style="color: #8fbcbb;"&gt;w11&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;w22&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;#255
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #88c0d0;"&gt;beq&lt;/span&gt;       &lt;span style="color: #8fbcbb;"&gt;LBB0_0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #88c0d0;"&gt;add&lt;/span&gt;       &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x21&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;,&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;x22&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The first few lines load up the 0 and stride mask vectors.&lt;/p&gt;
&lt;p&gt;The only strides I optimize are 1, -1, 2, -2, 4, -4, 8, and -8. At stride 16, you&amp;rsquo;re only getting one check per loop; might as well just do the basic iteration instead of firing up the vector unit.&lt;/p&gt;</description><author>Ashton Wiersdorf on Lambda Land</author><pubDate>Tue, 22 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2024-10-22_bf_writeup/</guid></item><item><title>Alternative origin of Batman - dream</title><link>https://robkohr.com/articles/alternative-origin-of-batman---dream</link><description>Alternative origin of Batman - dream</description><author>RobKohr's Blog</author><pubDate>Tue, 22 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/alternative-origin-of-batman---dream</guid></item><item><title>An experiment in fighting spam on public forms using “proof of work”</title><link>https://blog.ovalerio.net/archives/2996</link><description>Spam is everywhere. If you have an email account, a mailbox, a website with comments, a cellphone, a social media account, a public form, etc. We all know it, it is a plague. Over the years, there have been multiple attempts to fight spam, with various degrees of success, some more effective than others, some [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Mon, 21 Oct 2024 23:07:09 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/2996</guid></item><item><title>Typescript lets you spread an array into an object</title><link>https://blog.martijnarts.com/typescript-lets-you-spread-an-array-into-an-object/</link><description>&lt;p&gt;I ran into an instance of this&amp;#xa0;&lt;a href="https://github.com/microsoft/TypeScript/issues/9726" rel="noopener noreferrer"&gt;Typescript bug&lt;/a&gt;&amp;#xa0;recently. Basically, Typescript lets you do this without compiler errors:&lt;/p&gt;&lt;pre&gt;&lt;code class="language-typescript"&gt;const arr = [1, 2, 3];
const x: { a: number[] } = { a: { ...arr } };

x.a.map(n =&amp;gt; n + 1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note the &lt;code&gt;{ ...arr }&lt;/code&gt; instead of &lt;code&gt;[...arr]&lt;/code&gt;! Running this throws the error&lt;/p&gt;</description><author>Martijn Arts</author><pubDate>Mon, 21 Oct 2024 19:10:49 GMT</pubDate><guid isPermaLink="true">https://blog.martijnarts.com/typescript-lets-you-spread-an-array-into-an-object/</guid></item><item><title>Introducing the Cloud Debugger for Azure</title><link>https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/</link><description>&lt;p&gt;The Cloud Debugger is an open-source tool for Azure developers to explore, learn, and troubleshoot their Azure cloud environments. Whether preparing for Azure certification, looking to streamline debugging, or aiming to deepen your understanding of Azure, Cloud Debugger provides the tools to make the cloud more discoverable. Background &amp;#8211; Getting Azure certified As I prepared [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/"&gt;Introducing the Cloud Debugger for Azure&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Mon, 21 Oct 2024 19:10:27 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/azure/introducing-the-cloud-debugger-for-azure/</guid></item><item><title>TERMINAL ACCELERATION</title><link>https://asemic-horizon.com/2024/10/21/terminal-acceleration/</link><description>Definite terminality in its quality as definite terminality comes close enough to the process-philosophical notion of singularity that I can&amp;#8217;t actually tell them apart (at least at this moment in theory development). Terminality is the process by which differentiation is arrested; definite terminality is the attribute of not being able to further differentiate. Classic neurology [&amp;#8230;]</description><author>asemic horizon</author><pubDate>Mon, 21 Oct 2024 17:15:07 GMT</pubDate><guid isPermaLink="true">https://asemic-horizon.com/2024/10/21/terminal-acceleration/</guid></item><item><title>A walk after the storm</title><link>https://nicolaiarocci.com/a-walk-after-the-storm/</link><description>&lt;p&gt;A walk on the beach right after the storm. Lots of logs scattered all around, for miles.&lt;/p&gt;

&lt;div style="padding: 56.25% 0 0 0;"&gt;&lt;/div&gt;

&lt;p&gt;And sanderlings running all over the place.&lt;/p&gt;

&lt;div style="padding: 56.25% 0 0 0;"&gt;&lt;/div&gt;</description><author>Nicola Iarocci</author><pubDate>Mon, 21 Oct 2024 09:41:05 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/a-walk-after-the-storm/</guid></item><item><title>(Almost) Everything You Need to Run a Blog</title><link>https://lagomor.ph/2024/10/almost-everything-you-need-to-run-a-blog/</link><description>&lt;p&gt;We&amp;rsquo;re living in the cyber dark ages. The primary &lt;a href="https://en.wikipedia.org/wiki/List_of_most-visited_websites" rel="noopener noreferrer" target="_blank"&gt;way most people interact with the internet&lt;/a&gt; is through social media. This is the default space people not only listen to others, but also try to make their voices heard - which is sad, because social media isn&amp;rsquo;t very good for that.&lt;/p&gt;
&lt;p&gt;It is liberating to have complete control of your own cyberspace. I&amp;rsquo;ve been running &lt;a href="https://lagomor.ph" rel="noopener noreferrer" target="_blank"&gt;lagomor.ph&lt;/a&gt;, or some derivative of it, for almost ten years, and it&amp;rsquo;s been an incredibly fulfilling project - Without it, I would have missed out on countless opportunities to express myself and interact with interesting people - the number of friends I have made on account of this blog are countless.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Mon, 21 Oct 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/2024/10/almost-everything-you-need-to-run-a-blog/</guid></item><item><title>Unleashing the power of Change Data Capture (CDC) for Real-Time Data Syncing</title><link>https://engineeringatscale.substack.com/p/unleashing-the-power-of-change-data-capture-for-real-time-data-syncing</link><description>CDC - Working, Application and Challenges</description><author>Engineering At Scale</author><pubDate>Mon, 21 Oct 2024 07:40:26 GMT</pubDate><guid isPermaLink="true">https://engineeringatscale.substack.com/p/unleashing-the-power-of-change-data-capture-for-real-time-data-syncing</guid></item><item><title>Hide another detail</title><link>https://xenodium.com/hide-another-detail</link><description>&lt;p&gt;It's been 5 years since I talked about &lt;a href="https://xenodium.com/showhide-emacs-dired-details-in-style/"&gt;showing/hiding Emacs dired details in style&lt;/a&gt;, a short post showcasing &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Misc-Dired-Features.html"&gt;hide-details-mode&lt;/a&gt; (built-in) and &lt;a href="https://github.com/purcell/diredfl"&gt;diredfl&lt;/a&gt; (third-party).&lt;/p&gt;
&lt;p&gt;While my dired usage increased over the years, my dired config remained largely unchanged. Today, I'll show a new dired tweak.&lt;/p&gt;
&lt;p&gt;As you likely suspect by now, I'm a big fan of &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Misc-Dired-Features.html"&gt;hide-details-mode&lt;/a&gt;. It gives me super clean and minimalistic view of my files.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/hide-another-detail/before.png" /&gt;&lt;/p&gt;
&lt;p&gt;If I need more details, it's one toggle away using my trusty C-( binding.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/hide-another-detail/toggle-before.gif" /&gt;&lt;/p&gt;
&lt;p&gt;Now this is a super minor thing, but for a little while, I wished I could also hide the current directory's absolute path as part of &lt;code&gt;hide-details-mode&lt;/code&gt;'s toggling. In the same spirit as other hidden dired details, I rarely need to see the absolute path. And if I did, it'd only be a toggle away.&lt;/p&gt;
&lt;p&gt;With that in mind, I set out to bend dired my way. I looked at the &lt;code&gt;dired-hide-details-mode&lt;/code&gt; built-in code (dired.el) and came across invisibility specs, which I hadn't used before. Dired uses &lt;code&gt;add-to-invisibility-spec&lt;/code&gt; and &lt;code&gt;remove-from-invisibility-spec&lt;/code&gt; to show and hide details using the &lt;code&gt;invisible&lt;/code&gt; property set to &lt;code&gt;dired-hide-details-information&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now that we know what property to set, we need to find the text to apply it to. Dired offers that via &lt;code&gt;dired-subdir-regexp&lt;/code&gt;. All we need to do is match the regular expression and apply our invisible property to the relevant bounds.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun hide-dired-details-include-all-subdir-paths ()
  (save-excursion
    (goto-char (point-min))
    (while (re-search-forward dired-subdir-regexp nil t)
      (let* ((match-bounds (cons (match-beginning 1) (match-end 1)))
             (path (file-name-directory (buffer-substring (car match-bounds)
                                                          (cdr match-bounds))))
             (path-start (car match-bounds))
             (path-end (+ (car match-bounds) (length path)))
             (inhibit-read-only t))
        (put-text-property path-start path-end
                           'invisible 'dired-hide-details-information)))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All that's left is for us to add our new function to a hook, and we're good to go.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(use-package dired
  :hook ((dired-mode . dired-hide-details-mode)
         (dired-after-readin . hide-dired-details-include-all-subdir-paths)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;My Dired window is even cleaner now. The current directory's absolute path is now hidden.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/hide-another-detail/after.png" /&gt;&lt;/p&gt;
&lt;p&gt;There may be times we need to peek at the absolute path. We can now toggle hiding this detail just like the others.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/hide-another-detail/toggle-after.gif" /&gt;&lt;/p&gt;
&lt;h2&gt;My first Emacs patch&lt;/h2&gt;
&lt;p&gt;While this is a rather small change, I figured I could use it to get my toes dipped as a first Emacs contribution. I've since reworked the patch to fit into dired.el's code and submitted for review.&lt;/p&gt;
&lt;p&gt;I'm happy to report the tiny feature's now merged to master as of &lt;a href="https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=7cbca90569472af5643905fca5b7ab2dea67f876"&gt;a couple of days ago&lt;/a&gt;. Yay! 🎉&lt;/p&gt;
&lt;p&gt;It'll be sometime until the feature makes it to a release, but if you're living on the Emacs master edge, it should be available there. While the feature is disabled by default, it can enabled with:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(setq dired-hide-details-hide-absolute-location t)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Happy hiding!&lt;/p&gt;
&lt;h2&gt;Enjoying this content? Using one of my Emacs packages?&lt;/h2&gt;
&lt;p&gt;Help make the work sustainable. Consider &lt;a href="https://github.com/sponsors/xenodium"&gt;sponsoring&lt;/a&gt;. I'm also building &lt;a href="https://lmno.lol/"&gt;lmno.lol&lt;/a&gt;. A platform to &lt;a href="https://indieweb.social/@xenodium/112265481282475542"&gt;drag and drop&lt;/a&gt; your blog to the web.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Mon, 21 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/hide-another-detail</guid></item><item><title>From Text to Talk: Analyzing Open Source TTS Alternatives</title><link>https://www.tderflinger.com/en/text-to-talk-analyzing-open-source-tts-alternatives</link><description>All the big cloud providers like AWS and Azure have an API for the sythesis of text into the spoken word. But there are also young startups like ElevenLabs that offer their innovative solutions in this space. A third option is open source software for those who either do not want to pay for the service of TTS (text-to-speech) or do need on-device TTS.  That is why in this article I want to provide an overview of the most important open source TTS alternatives.</description><author>Thoughts by Thomas Derflinger</author><pubDate>Mon, 21 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.tderflinger.com/en/text-to-talk-analyzing-open-source-tts-alternatives</guid></item><item><title>Late Summering</title><link>https://faingezicht.com/photos/2024/10/21/summer-24/</link><description>A collection of trips with friends and family in the late summer of 2024. It took me a while to get through the rest of the black and white roll of film I'd started on our Jenner/Sea Rach trip that Nikhil and Rachel planned. I continued to shoot black and white in the city, and got to swap to color on our trip to Sequoia with Amol and Annabelle's crew. Then Gael showed up. Great summer all around.</description><author>Avy Faingezicht</author><pubDate>Mon, 21 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://faingezicht.com/photos/2024/10/21/summer-24/</guid></item><item><title>DynamoDB Considered Harmful</title><link>/dynamodb-harmful/</link><description>&lt;p&gt;I think DynamoDB is quite a useful database. But, I'm here to tell you should pretty much never use it whether in a greenfield project or a mature product. Let me explain why.&lt;/p&gt;
&lt;h3&gt;1. It's inflexibility will slow you down.&lt;/h3&gt;
&lt;p&gt;Initially, DynamoDB's speed and schema-less nature can make development fast. Although DynamoDB isn't modelled around schema's (like a traditional SQL database), it is modelled around queries. This means you need to know the query model up front. In a mature product, you might have a good idea of what possible queries you would want but in a greenfield product it's downright impossible. But, regardless of how mature your product is we all suffer from a fog of war. It's impossible to know what feature requests will be fired at us by a product manager. This is where DynamoDB will start to become as cumbersome as jeans in a rainstorm. Because you originally modelled the database around the queries you knew about, it becomes inflexible to change for the new queries you need to perform. Often times, teams just add new global secondary indexes (which behind the scenes is a complete copy of your database). But these are limited to 20 per table. And it creates another problem, deciding what index to use when. This becomes a headache to maintain and build upon.
Some may reason that they can create other tables around the new query model, or change the existing database. But these solutions are also rife with complexity. For example, DynamoDB has a 1MB limit on results from scans. Therefore, to write a migration script you'd need to loop through all this data, update it in memory, remove the old data and then write it back to the database. Try doing that in a no-downtime fashion.
SQL on the other hand can be mashed, broken, cracked and mangled in order to create whatever queries you want. You can join tables where there are separations of relationships. And if you ever get into place where the table design is bad you can do a SQL migration - it might take a while but there are no query limits to contend with.&lt;/p&gt;
&lt;h3&gt;2. You don't have the scale nor will ever need it&lt;/h3&gt;
&lt;p&gt;DynamoDB was designed with hyperscaler levels of traffic. Sites receiving this level of traffic clock in at less than 50 across the entire internet. Ergo, you do not need DynamoDB's scale. Even Facebook who started on MySQL (but eventually use other supporting databases), didn't need DynamoDB level scale (initially) and when they did they refactored. Choosing a database technology for scaling reasons is akin to getting &amp;quot;CEO&amp;quot; business cards before writing a line of code - it's premature optimisation.&lt;/p&gt;
&lt;h4&gt;&amp;quot;But what about scale?&amp;quot; I hear you cry&lt;/h4&gt;
&lt;p&gt;This is a non-argument made by people who haven't launched serious products. Any SQL database with enough money thrown at it can scale plenty fine. SQL databases power some of the most used websites in the world - including all Wordpress sites &lt;a href="https://colorlib.com/wp/wordpress-statistics/"&gt;(43.3% of all websites)&lt;/a&gt;, Spotify &lt;a href="https://the-cfo.io/2024/07/29/revenue-radar-spotify-hits-high-note-with-q2-2024-results-but-faces-industry-discord/"&gt;(626M MAU)&lt;/a&gt; and Twitter/X &lt;a href="https://www.demandsage.com/twitter-statistics/"&gt;(516M MAU)&lt;/a&gt;.
If you reach a scale where SQL becomes the blockage then you likely have the money to solve the problem either by refactoring parts of your app or mitigating the issues (with caches, increased horizontal capacity, sharding etc). In any case, scale is not a reason to discount SQL.&lt;/p&gt;
&lt;h3&gt;3. You need other technologies to handle basic database functions&lt;/h3&gt;
&lt;p&gt;Let's say you are using DynamoDB for your little CRUD app. Percy the product manager swaggers up to your desk and asks &amp;quot;Can we add search and sorting of tables to our product pretty please?&amp;quot;. After a few umms and ahhs, you probably realise that DynamoDB ain't gonna cut it. You're going to use another tool, like Opensearch or Algolia. Before any DynamoDB rage nerds ask, yes you can do search using &amp;quot;contains&amp;quot; and you can do sorting. But try doing something even remotely complex and it becomes impossible to perform. You know what could add search and sorting to your app, SQL! DynamoDB needs all this other cruft to provide basic functions to your app. And it's not just a case of spinning up some new system and hey presto it works. Nope! You've got to keep those systems in sync (DynamoDB streams or Kinesis), then you've got to configure indexes and so on.
Your technology choice is slowing you down from delivery of features to your customers. Guess who also doesn't care about your database - your customers!&lt;/p&gt;
&lt;h3&gt;4. It's challenging to work on your system locally&lt;/h3&gt;
&lt;p&gt;Working with DynamoDB locally isn't as simple as running a docker container (like ehem, MySQL or Postgres). So, then you're focused to have a &amp;quot;remote&amp;quot; development environment. Where you have resources deployed to the cloud that are used by each developer. These can work, but provide horrendous experiences. Changes to system configuration have to be deployed and you can't work offline. There are a whole host of problems that arise as a result of systems that cannot just be run on a computer.&lt;/p&gt;
&lt;h3&gt;In summary&lt;/h3&gt;
&lt;p&gt;DynamoDB isn't a bad database. It's simply a tool that came from a certain context - in Amazons case high throughput writes and reads. In 99% of cases, you are not going to be in that same context. Focus on getting there first with the power of a SQL database. Most apps are just CRUD, SQL is super good at that - use it, and get back to building the thing.&lt;/p&gt;
&lt;p&gt;PS: A good litmus test to see if your tech stack is working is asking &amp;quot;in the last 6 months to what extent has our technology impeded or prevented the development of new features or fixes?&amp;quot;. If the answer is &amp;gt; 3 then you probably chose wrong.&lt;/p&gt;</description><author/><pubDate>Mon, 21 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">/dynamodb-harmful/</guid></item><item><title>Debugging my wife's alarm clock</title><link>https://ntietz.com/blog/debugging-my-wifes-alarm-clock/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;My wife's alarm clock has been acting up lately.
Sporadic at first but then every day, it wouldn't blare in the morning at the set time.
Instead, when it was supposed to go off it would... reset itself.
The time would start flashing in that "I'm confused because the power went out" sort of way.&lt;/p&gt;
&lt;img src="https://ntietz.com/images/clock.gif" /&gt;
&lt;p&gt;Not very useful.
You want the alarm to wake you up, not give you a new morning chore.&lt;/p&gt;
&lt;p&gt;I volunteered to try to fix it: I'm pretty handy, and I've done some basic electronics repair before.
(Desperate times called for desperate measures, and I got that coffee grinder &lt;em&gt;working&lt;/em&gt; again with just a little soldering.)
Before opening it up, I pointed out that there's a battery.
We reproduced the issue first, then changed the battery, and confirmed that that was the culprit&lt;sup class="footnote-reference" id="fr-always-reproduce-1"&gt;&lt;a href="https://ntietz.com/blog/debugging-my-wifes-alarm-clock/#fn-always-reproduce"&gt;[1]&lt;/a&gt;&lt;/sup&gt;!&lt;/p&gt;
&lt;p&gt;But that left the question of &lt;em&gt;why&lt;/em&gt; it's doing that.
It probably isn't using the battery for power all the time, since it's plugged in.
And it does get its timekeeping from the wall—well, more on that later.
You can even hear a 60 Hz hum from it if you listen closely, just like our AC frequency in North America.&lt;/p&gt;
&lt;p&gt;I wanted to open it up, but there's this warning on it.
It says not to open it, and elsewhere it says "no user serviceable parts inside."
I could get &lt;em&gt;shocked&lt;/em&gt; if I open this.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/debugging-my-wifes-alarm-clock/&amp;#x2F;processed_images&amp;#x2F;alarm-1.f0e66261ece6253e.jpg" /&gt;
&lt;p&gt;So I just ignored that, and I took it apart and started poking around&lt;sup class="footnote-reference" id="fr-safety-1"&gt;&lt;a href="https://ntietz.com/blog/debugging-my-wifes-alarm-clock/#fn-safety"&gt;[2]&lt;/a&gt;&lt;/sup&gt; :D&lt;/p&gt;
&lt;p&gt;My opening hypothesis was that something had broken—maybe a capacitor, maybe just a bad solder joint—and when the alarm went off it somehow cut mains power and used the battery instead, resetting it.
This turned out to be &lt;em&gt;not&lt;/em&gt; it.&lt;/p&gt;
&lt;p&gt;First, let's appreciate how simple this whole thing is.
The alarm clock boils down to one integrated circuit, one 7-segment display panel, a transformer, and a pile of wires, resistors, capacitors, and that sort of thing.
Pretty neat, and definitely not something you see in many new products these days (this is over a decade old).&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/debugging-my-wifes-alarm-clock/&amp;#x2F;processed_images&amp;#x2F;alarm-2.c86bc022a6d610e0.jpg" /&gt;

&lt;img src="https://ntietz.com/blog/debugging-my-wifes-alarm-clock/&amp;#x2F;processed_images&amp;#x2F;alarm-3.a3734f3d4584ce21.jpg" /&gt;
&lt;p&gt;Poking around inside I didn't see any obvious damage, and there wasn't anything disconnected that should be connected.
I started to wonder if this was just... how it was designed.&lt;/p&gt;
&lt;p&gt;The IC powering the alarm is the LM8560, and &lt;a href="https://www.alldatasheet.com/datasheet-pdf/view/41215/SANYO/LM8560.html"&gt;its datasheet&lt;/a&gt; tells us a lot!
It's responsible for storing the time and handling the alarm clock's functionality, including snooze and time setting.
And it turns out that yes, the clock is using the wall for timekeeping, which is more reliable than many other sources of time&lt;sup class="footnote-reference" id="fr-depends-1"&gt;&lt;a href="https://ntietz.com/blog/debugging-my-wifes-alarm-clock/#fn-depends"&gt;[3]&lt;/a&gt;&lt;/sup&gt;.
And it &lt;em&gt;also&lt;/em&gt; has a crystal oscillator built-in which keeps the time when the mains power fails for at least 3 cycles, or 1/20th of a second.&lt;/p&gt;
&lt;p&gt;So, uh, what's happening here?
My best guess is that when the alarm goes off it's supposed to pull voltage from the battery to power the buzzer, but when the battery is dead, it pulls from AC and manages to drop voltage for the entire IC.
This leads it to trigger the "I lost power" display flash and also reset the time to 12:00, which doesn't happen when the battery is present and power is lost.&lt;/p&gt;
&lt;p&gt;This isn't reproducible by removing the battery entirely, either.
If you take it out, the clock loses all ability to function and just resets &lt;em&gt;constantly&lt;/em&gt;.
So having a &lt;em&gt;mostly&lt;/em&gt; drained battery seems to be doing a little work so that it resets once but then resumes normal functioning (until the next time it tries to go off).&lt;/p&gt;
&lt;p&gt;Another neat thing did happen after my debugging session.
I set the clock on the steps to take back upstairs, and a few minutes later I heard it go off.
Without being plugged in, everything is working (sans display, which does require mains power), and... it sounds &lt;em&gt;better&lt;/em&gt;?
With AC present, the buzzer sounds distorted and grungy.
When it's just on battery, it sounds very clean.
This makes me want to find a kit to play with analog synths.&lt;/p&gt;
&lt;p&gt;I can't say I'm a big fan of the design of this clock.
It is plugged into the wall, but has a load-bearing 9V battery!
If anyone knows for sure why this alarm clock does this weird thing with a load-bearing battery, I'd love to learn more!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 10/27: A few readers have pointed out to me that it's likely a bad capacitor, which cap it is, and how to test that and/or fix it! I'll try that out this week and update further.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Update 3/23: After months without progress (thanks, Lyme disease), I finally replaced the capacitor. The alarm clock is fully fixed now! Thank you to Asahi Lina and other readers who pointed this out to me!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But I should probably go put her alarm clock back now...&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-always-reproduce"&gt;
&lt;p&gt;&lt;em&gt;Always&lt;/em&gt; reproduce issues before you try to fix them.
If you don't know how to reproduce it, you don't know how to check that it's fixed! &lt;a href="https://ntietz.com/blog/debugging-my-wifes-alarm-clock/#fr-always-reproduce-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-safety"&gt;
&lt;p&gt;While I did this, I do generally know what I'm doing around mains power and am taking that risk for myself.
Don't do this without proper training and safety precautions.
The effects of high voltage can be quite shocking. &lt;a href="https://ntietz.com/blog/debugging-my-wifes-alarm-clock/#fr-safety-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-depends"&gt;
&lt;p&gt;The term to look for is &lt;a href="https://en.wikipedia.org/wiki/Utility_frequency#Time_error_correction_(TEC)"&gt;time error correction&lt;/a&gt;. Most places still do regulate frequency of mains power, but notable exceptions (Russia, China, etc.) exist.
Where I am in the US, the goal is 4,320,000 cycles per day, and the error is actively corrected whenever it exceeds 10 seconds cumulatively.
We're the most lax region of the US, and that's still going to be accurate enough for most needs. &lt;a href="https://ntietz.com/blog/debugging-my-wifes-alarm-clock/#fr-depends-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 21 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/debugging-my-wifes-alarm-clock/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>2024-10-21</title><link>https://ho.dges.online/pictures/2024-10-21/</link><description/><author>ho.dges.online</author><pubDate>Mon, 21 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-10-21/</guid></item><item><title>Virgin Voyages from Miami</title><link>https://digitalnomadder.micro.blog/2024/10/20/virgin-voyages-from.html</link><description>&lt;p&gt;We usually aren’t into cruises.  But we always seem to end up on one.  When we do go, it’s usually more to hang out with friends than the cruise itself.  This time was no different.&lt;/p&gt;
&lt;p&gt;We flew into Miami the day before and stayed at the &lt;a href="https://www.hyatt.com/hyatt-regency/en-US/miarm-hyatt-regency-miami"&gt;Hyatt Regency Miami&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://cdn.uploads.micro.blog/79953/2024/12a7adf9a5.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I don’t really have too much to say about the cruise.  Just read the &lt;a href="https://travel.usnews.com/cruises/virgin-voyages-1045/"&gt;review&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It’s an all adult cruise.  There isn’t as much entertainment as Carnival Cruises.  Depending on your point of view, it’s either boring or relaxing.&lt;/p&gt;
&lt;p&gt;The food is definitely better.  Another difference is that with Carnival, every adult in your cabin has to buy a &lt;a href="https://help.carnival.com/app/answers/detail/a_id/3525/~/cheers%21-beverage-program-q%26a"&gt;Cheers Beverage Package&lt;/a&gt; if one person buys one.&lt;/p&gt;
&lt;p&gt;With Virgin, you can open an optional &lt;a href="https://www.virginvoyages.com/cruise-deals/bar-tab-offer"&gt;bar tab&lt;/a&gt; that gives you bonus spend.&lt;/p&gt;
&lt;p&gt;You also get free basic WiFI.&lt;/p&gt;
&lt;p&gt;As far as the stops,  we stopped in the Dominican Republic and did a rum tour…&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://cdn.uploads.micro.blog/79953/2024/a383dc1145.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;We had a day at sea and then stopped at the &lt;a href="https://www.myoutislands.com/bahamas-islands/bimini/beaches"&gt;THE BEACHES OF BIMINI, THE BAHAMAS&lt;/a&gt;.  We just went to the beach and hung out in the water.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://cdn.uploads.micro.blog/79953/2024/5e1ca6f09b.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;This is very much a been there done that thing.  It was…fine.  But we probably won’t do Virgin Voyages again. Vir&lt;/p&gt;</description><author>The Digital Nomad</author><pubDate>Mon, 21 Oct 2024 01:35:34 GMT</pubDate><guid isPermaLink="true">https://digitalnomadder.micro.blog/2024/10/20/virgin-voyages-from.html</guid></item><item><title>ADB helper</title><link>https://hth.is/2024/10/20/adb-helper/</link><description>tl;dr: I&amp;rsquo;ve rewritten most of my adb related bash scripts and aliases that have accumulated over the years into a single tool that can be found here.
Beginnings Link to heading Fifteen years ago I started work on creating my very first Android app.
It was part of a university computer science course designed to guide us through the complete software development life cycle — design, gathering requirements, implementation, and deployment — rather than the focus on fragmented software concepts that other classes had.</description><author>Hrafn Thorvaldsson</author><pubDate>Sun, 20 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://hth.is/2024/10/20/adb-helper/</guid></item><item><title>Astrophysics for People in a Hurry</title><link>https://vit.baisa.cz/books/astrophysics-for-people-in-a-hurry/</link><description>a brief intro to the universe</description><author>Vít Baisa</author><pubDate>Sun, 20 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/books/astrophysics-for-people-in-a-hurry/</guid></item><item><title>2024-10-20</title><link>https://ho.dges.online/pictures/2024-10-20/</link><description/><author>ho.dges.online</author><pubDate>Sun, 20 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-10-20/</guid></item><item><title>Walking the London LOOP</title><link>https://michael-lewis.com/posts/walking-the-london-loop/</link><description>&lt;h2 id="introduction"&gt;
  Introduction
  &lt;a class="heading-link" href="#introduction"&gt;
    &lt;i class="fa-solid fa-link" title="Link to heading"&gt;&lt;/i&gt;
    &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;
  &lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The London LOOP is the London Outer Orbital Path, a 150 mile (242km) walking route which encircles the city, like a hiking equivalent of the M25 (the London Orbital Motorway). There&amp;rsquo;s more good info on the LOOP on the &lt;a class="external-link" href="https://tfl.gov.uk/modes/walking/loop-walk" rel="noopener" target="_blank"&gt;TFL&lt;/a&gt; and &lt;a class="external-link" href="https://www.innerlondonramblers.org.uk/ideasforwalks/loop-guides.html" rel="noopener" target="_blank"&gt;Ramblers&lt;/a&gt; sites.&lt;/p&gt;
&lt;p&gt;I split the walk over 9 days between May and September 2024, averaging 15-20 miles (24-32km) per day. These are some notes I took.&lt;/p&gt;</description><author>Michael Ian Lewis</author><pubDate>Sun, 20 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://michael-lewis.com/posts/walking-the-london-loop/</guid></item><item><title>The Unreasonable Effectiveness of Monte Carlo Simulations in A/B Testing</title><link>https://bytepawn.com/unreasonable-effectiveness-monte-carlo-ab-testing.html</link><description>&lt;p&gt;The article illustrates how Monte Carlo simulations serve as a powerful method for exploring statistical concepts in A/B testing, enhancing understanding, improving experimental design and analysis.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Monte Carlo casino" src="https://www.1000lonelyplaces.com/wp-content/uploads/2017/03/man-and-woman-playing-Roulette-1024x680.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 20 Oct 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/unreasonable-effectiveness-monte-carlo-ab-testing.html</guid></item><item><title>Running Vision Encoder Decoder Models in Swift (or any language)</title><link>https://web.navan.dev/posts/2024-10-19-vision-encoder-decoder-swift-onnx-coreml.html</link><description>Basically an info dump on Vision Encoder Decoder Transformers and how you can run them in Swift using ONNX Runtime, and also how to convert them using coremltools</description><author>Navan's Archive</author><pubDate>Sat, 19 Oct 2024 23:46:00 GMT</pubDate><guid isPermaLink="true">https://web.navan.dev/posts/2024-10-19-vision-encoder-decoder-swift-onnx-coreml.html</guid></item><item><title>How I Defeated An MMO Game Hack Author</title><link>https://thecodist.com/how-i-defeated-an-mmo-game-hack-author/</link><description>&lt;p&gt;In the late 2000&amp;apos;s, I worked at a niche MMO game company. We had a small team, not a lot of money, but a loyal audience. It was a game of skill without any of the usual powerups and unreality, and the players enjoyed the challenge.&lt;/p&gt;&lt;p&gt;Then, one&lt;/p&gt;</description><author>The Codist</author><pubDate>Sat, 19 Oct 2024 20:25:59 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/how-i-defeated-an-mmo-game-hack-author/</guid></item><item><title>Album Review - Modern Vampires of the City</title><link>https://siddhesh.substack.com/p/mvotc</link><description>"Wisdom's a gift, but you'd trade it for youth / Age is an honor, it's still not the truth"</description><author>Obvious Bicycle</author><pubDate>Sat, 19 Oct 2024 20:19:43 GMT</pubDate><guid isPermaLink="true">https://siddhesh.substack.com/p/mvotc</guid></item><item><title>Big Plane</title><link>http://blog.jonandnic.com/2024/10/19/big-plane/</link><description>Copyright 2024 jonandnic.comAll Rights Reserved</description><author>jonandnic dot com</author><pubDate>Sat, 19 Oct 2024 19:57:02 GMT</pubDate><guid isPermaLink="true">http://blog.jonandnic.com/2024/10/19/big-plane/</guid></item><item><title>One more for the road</title><link>http://blog.jonandnic.com/2024/10/19/one-more-for-the-road/</link><description>Upgrading from a 2016 BMW i3 to a 2023 Chevy Bolt EUV for more range, and hopefully less hassle. But the real question is, can it run on DIY sun power?</description><author>jonandnic dot com</author><pubDate>Sat, 19 Oct 2024 16:51:19 GMT</pubDate><guid isPermaLink="true">http://blog.jonandnic.com/2024/10/19/one-more-for-the-road/</guid></item><item><title>Chocolate Tempering</title><link>https://www.unterminated.com/food/chocolate-tempering</link><description>Tempering is necessary to give chocolate a nice appearance and long shelf life. I'll go over what I've learned about the process, and the techniques I use for doing it at home.</description><author>Unterminated</author><pubDate>Sat, 19 Oct 2024 08:08:27 GMT</pubDate><guid isPermaLink="true">https://www.unterminated.com/food/chocolate-tempering</guid></item><item><title>Update to my philosophy: less resolutely materialist and utilitarian</title><link>https://evanfields.net/Philosophically-Weaker/</link><description>Like many people with my demographics, I’ve long had strongly materialist and utilitarian philosophical intuitions. I think empirical science is a good way to learn about the world; material explanations suffice for most phenomena we encounter in everyday life; more people having more well-being is better than fewer people have less well-being. But over the last two-ish years, these intuitions have unmistakably softened. I attribute this change to several causes, roughly ordered: My friend Doug Kremm, a wise and patient philosopher; Philosophy and philosophers I’ve encountered through Effective Altruist-adjacent media, especially Eric Schwitzgebel; General contemplation and mellowing with age.</description><author>Evan Fields</author><pubDate>Sat, 19 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://evanfields.net/Philosophically-Weaker/</guid></item><item><title>The Ultimate Fujifilm X-E1 Camera Guide</title><link>https://thomashunter.name/posts/2024-10-19-fujifilm-xe1-ttartisan-25-review</link><author>Thomas Hunter II</author><pubDate>Sat, 19 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://thomashunter.name/posts/2024-10-19-fujifilm-xe1-ttartisan-25-review</guid></item><item><title>Programming an FPGA with a FOSS toolchain</title><link>http://blog.peramid.es/rss.xml/posts/2024-10-19-fpga.html</link><description>&lt;p&gt;This is a quick guide on how to program a Sipeed &lt;a href="https://wiki.sipeed.com/hardware/en/tang/Tang-Nano-9K/Nano-9K.html"&gt;Tang Nano 9K&lt;/a&gt; (GOWIN GW1NR-9) using a fully open source toolchain for FPGA programming. I will use Arch Linux but you may find some of the required packages available in your distro’s package repository as well.&lt;/p&gt;
&lt;p&gt;Install &lt;a href="https://yosyshq.net/yosys/"&gt;Yosys&lt;/a&gt; and &lt;a href="https://github.com/trabucayre/openFPGALoader"&gt;OpenFPGALoader&lt;/a&gt; from the official Arch repository with &lt;em&gt;pacman&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo pacman -S yosys openfpgaloader&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you will need to install &lt;a href="https://github.com/YosysHQ/nextpnr"&gt;nextpnr&lt;/a&gt; and &lt;a href="https://github.com/YosysHQ/apicula"&gt;Project Apicula&lt;/a&gt; from AUR. I had a few problems with these packages so I will explain what I’ve done to make it work for me, but try a normal installation first as these problems will likely get fixed in the near future.&lt;/p&gt;
&lt;p&gt;First download and install my &lt;code&gt;apicula-git&lt;/code&gt; package from &lt;a href="https://aur.archlinux.org/packages/apicula-git"&gt;AUR&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Then download the &lt;code&gt;nextpnr-git&lt;/code&gt; package, also from &lt;a href="https://aur.archlinux.org/packages/nextpnr-git"&gt;AUR&lt;/a&gt;, and edit the &lt;code&gt;PKGBUILD&lt;/code&gt; file to only build your target:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;_ARCHS=('himbaechel')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And comment out this dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    himbaechel)
      #makedepends+=('prjapicula')&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you should be able to &lt;code&gt;makepkg -i&lt;/code&gt;, this will take some time so you can brew a mate meanwhile. If everything has gone well you should be able to run &lt;code&gt;nextpnr-himbaechel --version&lt;/code&gt; successfully.&lt;/p&gt;
&lt;p&gt;At this point you have everything needed to synthesise, route&amp;amp;place, generate bitstreams and upload them into your Tang Nano. Let’s try out the &lt;a href="https://wiki.sipeed.com/hardware/en/tang/Tang-Nano-9K/examples/led.html"&gt;LED example&lt;/a&gt; from the Sipeed wiki.&lt;/p&gt;
&lt;p&gt;In a new directory create a &lt;code&gt;led.v&lt;/code&gt; Verilog file with the following code:&lt;/p&gt;
&lt;div class="sourceCode" id="cb4"&gt;&lt;pre class="sourceCode verilog"&gt;&lt;code class="sourceCode verilog"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;module&lt;/span&gt; led &lt;span class="op"&gt;(&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="dt"&gt;input&lt;/span&gt; clk&lt;span class="op"&gt;,&lt;/span&gt;              &lt;span class="co"&gt;// clk input&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="dt"&gt;input&lt;/span&gt; rst&lt;span class="op"&gt;,&lt;/span&gt;              &lt;span class="co"&gt;// reset input&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-4"&gt;&lt;a href="#cb4-4" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="dt"&gt;output&lt;/span&gt; &lt;span class="dt"&gt;reg&lt;/span&gt; &lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="dv"&gt;5&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt;&lt;span class="dv"&gt;0&lt;/span&gt;&lt;span class="op"&gt;]&lt;/span&gt; led    &lt;span class="co"&gt;// 6 LEDS pin&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-5"&gt;&lt;a href="#cb4-5" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;);&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-6"&gt;&lt;a href="#cb4-6" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb4-7"&gt;&lt;a href="#cb4-7" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;reg&lt;/span&gt; &lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="dv"&gt;23&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt;&lt;span class="dv"&gt;0&lt;/span&gt;&lt;span class="op"&gt;]&lt;/span&gt; counter&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-8"&gt;&lt;a href="#cb4-8" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb4-9"&gt;&lt;a href="#cb4-9" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;always&lt;/span&gt; &lt;span class="op"&gt;@(&lt;/span&gt;&lt;span class="kw"&gt;posedge&lt;/span&gt; clk &lt;span class="dt"&gt;or&lt;/span&gt; &lt;span class="kw"&gt;negedge&lt;/span&gt; rst&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="kw"&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-10"&gt;&lt;a href="#cb4-10" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;if&lt;/span&gt; &lt;span class="op"&gt;(!&lt;/span&gt;rst&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-11"&gt;&lt;a href="#cb4-11" tabindex="-1"&gt;&lt;/a&gt;        counter &lt;span class="op"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="bn"&gt;24'd0&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-12"&gt;&lt;a href="#cb4-12" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;else&lt;/span&gt; &lt;span class="kw"&gt;if&lt;/span&gt; &lt;span class="op"&gt;(&lt;/span&gt;counter &lt;span class="op"&gt;&amp;lt;&lt;/span&gt; &lt;span class="bn"&gt;24'd1349_9999&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;       &lt;span class="co"&gt;// 0.5s delay&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-13"&gt;&lt;a href="#cb4-13" tabindex="-1"&gt;&lt;/a&gt;        counter &lt;span class="op"&gt;&amp;lt;=&lt;/span&gt; counter &lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="bn"&gt;1'b1&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-14"&gt;&lt;a href="#cb4-14" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-15"&gt;&lt;a href="#cb4-15" tabindex="-1"&gt;&lt;/a&gt;        counter &lt;span class="op"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="bn"&gt;24'd0&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-16"&gt;&lt;a href="#cb4-16" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-17"&gt;&lt;a href="#cb4-17" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb4-18"&gt;&lt;a href="#cb4-18" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;always&lt;/span&gt; &lt;span class="op"&gt;@(&lt;/span&gt;&lt;span class="kw"&gt;posedge&lt;/span&gt; clk &lt;span class="dt"&gt;or&lt;/span&gt; &lt;span class="kw"&gt;negedge&lt;/span&gt; rst&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="kw"&gt;begin&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-19"&gt;&lt;a href="#cb4-19" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;if&lt;/span&gt; &lt;span class="op"&gt;(!&lt;/span&gt;rst&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-20"&gt;&lt;a href="#cb4-20" tabindex="-1"&gt;&lt;/a&gt;        led &lt;span class="op"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="bn"&gt;6'b111110&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-21"&gt;&lt;a href="#cb4-21" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;else&lt;/span&gt; &lt;span class="kw"&gt;if&lt;/span&gt; &lt;span class="op"&gt;(&lt;/span&gt;counter &lt;span class="op"&gt;==&lt;/span&gt; &lt;span class="bn"&gt;24'd1349_9999&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;       &lt;span class="co"&gt;// 0.5s delay&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-22"&gt;&lt;a href="#cb4-22" tabindex="-1"&gt;&lt;/a&gt;        led&lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="dv"&gt;5&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt;&lt;span class="dv"&gt;0&lt;/span&gt;&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;led&lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="dv"&gt;4&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt;&lt;span class="dv"&gt;0&lt;/span&gt;&lt;span class="op"&gt;],&lt;/span&gt;led&lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="dv"&gt;5&lt;/span&gt;&lt;span class="op"&gt;]};&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-23"&gt;&lt;a href="#cb4-23" tabindex="-1"&gt;&lt;/a&gt;    &lt;span class="kw"&gt;else&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-24"&gt;&lt;a href="#cb4-24" tabindex="-1"&gt;&lt;/a&gt;        led &lt;span class="op"&gt;&amp;lt;=&lt;/span&gt; led&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-25"&gt;&lt;a href="#cb4-25" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;end&lt;/span&gt;&lt;/span&gt;
&lt;span id="cb4-26"&gt;&lt;a href="#cb4-26" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt;
&lt;span id="cb4-27"&gt;&lt;a href="#cb4-27" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;endmodule&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You will need a CST file for this board; you can find an example in the &lt;a href="https://github.com/YosysHQ/apicula/blob/master/examples/tangnano9k.cst"&gt;Apicula’s repo&lt;/a&gt; but you can also use this fragment (name it &lt;code&gt;tangnano9k.cst&lt;/code&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IO_LOC &amp;quot;clk&amp;quot; 52;
IO_LOC &amp;quot;led[0]&amp;quot; 10;
IO_LOC &amp;quot;led[1]&amp;quot; 11;
IO_LOC &amp;quot;led[2]&amp;quot; 13;
IO_LOC &amp;quot;led[3]&amp;quot; 14;
IO_LOC &amp;quot;led[4]&amp;quot; 15;
IO_LOC &amp;quot;led[5]&amp;quot; 16;
IO_LOC &amp;quot;key&amp;quot; 3;
IO_LOC &amp;quot;rst&amp;quot; 4;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You are ready to synthesise this design with the following &lt;em&gt;yosys&lt;/em&gt; script:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yosys -p &amp;quot;read_verilog led.v; synth_gowin -top led -json led.json&amp;quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the synthesis completes successfully there will be a new &lt;code&gt;led.json&lt;/code&gt; file. Next let’s use &lt;em&gt;nextpnr&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nextpnr-himbaechel --json led.json --write pnrled.json --device GW1NR-LV9QN88PC6/I5 --vopt family=GW1N-9C --vopt cst=tangnano9k.cst&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After a few seconds you should see a message saying &lt;em&gt;Program finished normally&lt;/em&gt; and there should be a new &lt;code&gt;pnrled.json&lt;/code&gt; file that we will use to generate the bitstream:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gowin_pack -d GW1N-9C -o led.fs pnrled.json&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now it’s time to grab your dev board :D&lt;/p&gt;
&lt;p&gt;Connect the Tang Nano 9K to a USB-C and try this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openFPGALoader --scan-usb&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should list an FT2232 &lt;em&gt;JTAG Debugger&lt;/em&gt;, if it doesn’t then try to unplug and plug again (and avoid sketchy USB hubs ^_^). If you do see the debugger then you can run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openFPGALoader --detect&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This should list a Gowin device and it means we are ready to upload the bitstream:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;openFPGALoader -b tangnano9k -f led.fs&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once it’s finished, the six tiny LEDs next to the FPGA should be turning on progressively.&lt;/p&gt;</description><author>pera's blog</author><pubDate>Sat, 19 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://blog.peramid.es/rss.xml/posts/2024-10-19-fpga.html</guid></item><item><title>A Guide to CA's Props and San Mateo's Measure T</title><link>https://xavd.id/blog/post/2024-voters-guide/</link><description>A quick overview of what's on California's ballot in November 2024&lt;br /&gt;&lt;br /&gt;&lt;a href="https://xavd.id/blog/post/2024-voters-guide/"&gt;Read the whole thing&lt;/a&gt;.</description><author>The David Brownman Blog</author><pubDate>Sat, 19 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xavd.id/blog/post/2024-voters-guide/</guid></item><item><title>Copilot+ PC Review: Windows on ARM is finally here</title><link>https://jasoneckert.github.io/myblog/windows-on-arm/</link><description>&lt;p&gt;&lt;img alt="Copilot+ PC Desktop" src="desktop.png#center" title="Copilot+ PC Desktop" /&gt;&lt;/p&gt;
&lt;p&gt;Back in 2017, I wrote a &lt;a href="../get-ready-for-the-chip-wars/"&gt;blog post&lt;/a&gt; predicting intense competition between Intel x86-based and ARM-based PCs. And finally, after many years of lukewarm development efforts on the part of Microsoft and Qualcomm, that competition is here with Windows 11 Copilot+ PCs running powerful ARM-based Qualcomm Snapdragon X Elite CPUs with built in graphics and AI acceleration.&lt;/p&gt;
&lt;p&gt;As shown in the desktop screenshot at the top of this post I&amp;rsquo;ve been using a Copilot+ PC as a productivity and development workstation this month, and I must say that I&amp;rsquo;m thoroughly impressed overall. In this blog post, I&amp;rsquo;ll outline why.&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Sat, 19 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/windows-on-arm/</guid></item><item><title>@mtorygreen I'm current the CTO at Grove and Head of Protocol at Pocket Network.</title><link>https://olshansky.info/posts/2024-10-18-mtorygreen-im-current-the-cto-at-grove-and-head-of-protocol-at-pocket-network/</link><description>We're launching a full rewrite of Pocket early next year (a migration fully rewritten using the latest Cosmos SDK).</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 19 Oct 2024 01:34:31 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/2024-10-18-mtorygreen-im-current-the-cto-at-grove-and-head-of-protocol-at-pocket-network/</guid></item><item><title>How to Configure a Schlage Encode Plus for Home Assistant, HomeKit, Apple Home, and Apple Wallet Keys</title><link>https://jasonraimondi.com/posts/configure-a-schlage-encode-for-home-assistant-homekit-apple-home-apple-wallet-keys/</link><description>&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;After a year of using the Schlage Encode Plus lock with Home Assistant via the &lt;a href="https://github.com/mcnutter1/homeasssitant-schlage"&gt;homeassistant-schlage&lt;/a&gt; custom integration, I&amp;rsquo;ve finally cracked the code on seamlessly integrating it with Apple Home and enabling tap-to-open functionality with Apple Watch and iPhone. This guide will walk you through the setup process, ensuring your smart lock works flawlessly across multiple platforms.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;/h2&gt;
&lt;p&gt;Before we begin, make sure you have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An iPhone (iPad or Mac may work, but this is unconfirmed)&lt;/li&gt;
&lt;li&gt;A Schlage Encode Plus lock (&lt;a href="https://www.schlage.com/en/home/smart-locks/encode-plus.html"&gt;Official Website&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;An &lt;a href="https://support.apple.com/en-us/102557"&gt;Apple Home Hub&lt;/a&gt; (e.g., Apple TV 4K)&lt;/li&gt;
&lt;li&gt;Home Assistant with the &lt;a href="https://www.home-assistant.io/integrations/homekit/"&gt;HomeKit Bridge integration&lt;/a&gt; configured&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="tip tip--warning"&gt;
    &lt;span class="tip--title"&gt;&lt;/span&gt;
    &lt;strong&gt;Important:&lt;/strong&gt; If you&amp;rsquo;ve previously set up your lock using the Schlage app, you must factory reset it (press the button on the lock for 10 seconds) and remove it from the Schlage app. We won&amp;rsquo;t be using the Schlage app for this setup.
&lt;/div&gt;

&lt;h2 id="setup-process"&gt;Setup Process&lt;/h2&gt;
&lt;p&gt;Follow these steps to configure your Schlage Encode Plus for use with Home Assistant, HomeKit, and Apple Wallet:&lt;/p&gt;</description><author>Jason Raimondi on Software Engineer</author><pubDate>Fri, 18 Oct 2024 22:37:00 GMT</pubDate><guid isPermaLink="true">https://jasonraimondi.com/posts/configure-a-schlage-encode-for-home-assistant-homekit-apple-home-apple-wallet-keys/</guid></item><item><title>The future of Postgres?</title><link>/2024/10/18/The-future-of-Postgres/</link><description>&lt;p&gt;I&amp;rsquo;m often asked what do I think the &lt;em&gt;future for Postgres&lt;/em&gt; holds, and my answer has been mostly the same for probably 8 years now, maybe even longer. You see for Postgres itself stability and reliability is core. So where does the new stuff come from if it&amp;rsquo;s not in the stable core&amp;hellip; &lt;strong&gt;extensions&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Extensions within Postgres are unlike most other databases allowing you to modify or well extend the standard Postgres behavior. You can build other storage backends, new types, etc. Postgres itself ships with a number of extensions within the &amp;ldquo;contrib&amp;rdquo;. The list of contrib extensions hasn&amp;rsquo;t changed any time recently, but even contrib is a small sampling of what is possible. Beyond core there is a whole world of extensions, I want dig into just a smalls sampling starting with a few in core&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.crunchydata.com/blog/tentative-smarter-query-optimization-in-postgres-starts-with-pg_stat_statements"&gt;pg_stat_statements&lt;/a&gt;&lt;/strong&gt; is to me the most useful extension that exists. It records what queries were run, how long they took, and a number of other details about the queries. A key extension for managing performance of your database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.postgresql.org/docs/current/auto-explain.html"&gt;auto_explain&lt;/a&gt;&lt;/strong&gt; another one in contrib that is helpful for performance. For queries that run over a certain period of time will automatically log the explain plan–helpful for performance debugging.&lt;/p&gt;
&lt;p&gt;pg_prewarm useful to prewarming the cache ahead of a failover.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s jump out of contrib a little bit now. Of course that isn&amp;rsquo;t everything that ships with Postgres there is more, explore for yourself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/citusdata/citus"&gt;Citus&lt;/a&gt;&lt;/strong&gt; is one of the (to date) more advanced extensions ever created. Citus turns postgres into a sharded, distributed, horizontally scalable database. Citus is especially built to work well for B2B style multi-tenant apps, and now after being acquired years ago part of Microsoft.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/paradedb/paradedb"&gt;Pg_search&lt;/a&gt;&lt;/strong&gt; extends Postgres to support elastic-quality full text search directly within Postgres. I often say Postgres can do just about everything and be pretty capable. Things like time series and search it&amp;rsquo;s about 80% as good of some of the best in class options out there, but pg_search takes it further making it a full competitor to elastic, but Postgres.&lt;/p&gt;
&lt;p&gt;Those are some bigger ones, but you&amp;rsquo;ve also got a lot of small focused ones as well.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/citusdata/pg_cron"&gt;Pg_cron&lt;/a&gt;&lt;/strong&gt; is incredibly handy. Created originally by &lt;a href="https://twitter.com/marcoslot"&gt;@marcoslot&lt;/a&gt; while at Citus, it&amp;rsquo;s a small extension that does what it sounds like run scheduled jobs within Postgres. It is now a standard across all the major cloud providers. We leverage it heavily at &lt;a href="https://twitter.com/crunchydata"&gt;@crunchydata&lt;/a&gt;, but went a little further and built a UI on top so you don&amp;rsquo;t have to worry about crontab styling of the jobs.&lt;/p&gt;
&lt;p&gt;Just yesterday at Crunchy Data we released &lt;strong&gt;&lt;a href="https://github.com/CrunchyData/pg_parquet"&gt;pg_parquet&lt;/a&gt;&lt;/strong&gt;. There have been some questions about what about other extensions that sort of do similar but not exact but also do a lot of other things. This allows you to seamless copy to and from Parquet files with Postgres. It follows the linux philosophy of small sharp tools. Marco described the why extremely well:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We wanted to create a light-weight implementation of Parquet that does not pull a multi-threaded library into every postgres process. When you get to more complex features, a lot of questions around trade-offs, user experience, and deployment model start appearing.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;There is a whole lot of extensions out there. There are big robust ones that have been around forever (yeah I&amp;rsquo;m talking about you PostGIS), there are ones in contrib, there are ones that are literally created as jokes. Some try to do a lot others as I mentioned very narrow focus like pg_cron. Regardless of whether you believe in large complex extensions that do a lot or small ones that do one thing but do it really well, if you want to know where the future of Postgres is I can&amp;rsquo;t imagine a world where extensions do not play a heavy roll.&lt;/p&gt;</description><author>CRAIG KERSTIENS</author><pubDate>Fri, 18 Oct 2024 19:50:56 GMT</pubDate><guid isPermaLink="true">/2024/10/18/The-future-of-Postgres/</guid></item><item><title>CDC Is a Feature Not a Product</title><link>https://www.morling.dev/blog/cdc-is-a-feature-not-a-product/</link><description>&lt;div class="paragraph"&gt;
&lt;p&gt;During and after my time as the lead of &lt;a href="https://debezium.io/"&gt;Debezium&lt;/a&gt;,
a widely used open-source platform for Change Data Capture (CDC) for a variety of database,
I got repeatedly asked whether I’d be interested in creating a company around CDC.
VCs, including wellknown household names, did and do reach out to me,
pitching this idea.&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Fri, 18 Oct 2024 16:55:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/cdc-is-a-feature-not-a-product/</guid></item><item><title>Roast #42: Coffee Club V8</title><link>https://thoughtfulcoffeenyc.substack.com/p/roast-42-coffee-club-v8</link><description>The one with all the shipping issues</description><author>thoughtfulcoffee</author><pubDate>Fri, 18 Oct 2024 16:32:54 GMT</pubDate><guid isPermaLink="true">https://thoughtfulcoffeenyc.substack.com/p/roast-42-coffee-club-v8</guid></item><item><title>Practical Deep Learning, Lesson 3, Stochastic Gradient Descent on the Titanic Dataset</title><link>https://www.danielcorin.com/til/fastai/lesson3-sgd-titanic/</link><description>Practical Deep Learning, Lesson 3, Stochastic Gradient Descent on the Titanic Dataset</description><author>Thought Eddies</author><pubDate>Fri, 18 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/fastai/lesson3-sgd-titanic/</guid></item><item><title>Sonic Cafe, or how to caffeinate studio speakers</title><link>https://www.rollc.at/posts/2024-10-18-sonic-cafe/</link><description>&lt;p&gt;I like smart home stuff - in moderation. Some things end up too smart
for my liking. So I got rid of my &lt;a href="https://support.apple.com/en-us/111994"&gt;Homepods&lt;/a&gt; and got me a pair of &lt;a href="https://www.krkmusic.com/products/classic-5-powered-studio-monitor"&gt;KRK Classic 5&lt;/a&gt;s. These studio
monitors sound great, and are fantastic value for their price, but
have one annoying feature: the built-in circuitry that shuts them down
when there&amp;rsquo;s no audio playing. It makes sense in a studio context, but
sometimes I just like to listen to music quietly, and the shutdown
threshold is just a little bit too low.&lt;/p&gt;
&lt;p&gt;Some people have &lt;a href="https://www.youtube.com/watch?v=cGL0-jlc0oU"&gt;physically modded their speakers&lt;/a&gt; to permanently disable that feature, but I&amp;rsquo;d prefer to
keep my warranty for now, and I actually do like auto-shutdown - just
on my own terms.&lt;/p&gt;
&lt;p&gt;Nothing reinforces one&amp;rsquo;s claim to a hacker badge than solving hardware
problems in software (or vice versa). I wanted something like macOS&amp;rsquo;s
&lt;a href="https://ss64.com/mac/caffeinate.html"&gt;&lt;code&gt;caffeinate(1)&lt;/code&gt;&lt;/a&gt; command, except for audio.&lt;/p&gt;</description><author>rollcat's website</author><pubDate>Fri, 18 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.rollc.at/posts/2024-10-18-sonic-cafe/</guid></item><item><title>How to make an accordion with React Native</title><link>https://whackylabs.com/js/reactnative/ui/2024/10/17/how-to-make-an-accordion-with-reactnative/</link><description>&lt;p&gt;In the journey of making complex animations with React Native, let’s make an accordion!&lt;/p&gt;

&lt;p&gt;&lt;img alt="meme" src="/assets/accordion-reactnative/meme.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;I don’t know what is the best term for this component. I call it accordion, others call it expandable list. In the first version of material design this component was called as &lt;a href="https://m1.material.io/components/expansion-panels.html"&gt;“Expansion Panel”&lt;/a&gt;. In the latest version they simply don’t talk about it anymore. In Apple’s Human Interface Guidelines they named it as &lt;a href="https://developer.apple.com/design/human-interface-guidelines/disclosure-controls"&gt;“Disclosure controls”&lt;/a&gt; and in SwiftUI for some reasons they called it &lt;code class="language-plaintext highlighter-rouge"&gt;DisclosureGroup&lt;/code&gt;. But the &lt;a href="https://developer.apple.com/documentation/SwiftUI/DisclosureGroup"&gt;documentation writer&lt;/a&gt; described the component as:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;A view that shows or hides another content view, based on the state of a disclosure control.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Yes that is exactly what I mean.&lt;/p&gt;

&lt;p&gt;I’m going to use the assets from the &lt;a href="https://www.frontendmentor.io/challenges/faq-accordion-wyfFdeBwBz"&gt;frontendmentor challenge&lt;/a&gt; because it looks nice.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Design" src="/assets/accordion-reactnative/mobile-design.jpg" /&gt;&lt;/p&gt;

&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;
&lt;p&gt;To begin, inspired by Android I dumped all the data under one true global variable called &lt;code class="language-plaintext highlighter-rouge"&gt;R&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-js highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;white&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hsl(0, 0%, 100%)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lightPink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hsl(275, 100%, 97%)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;grayishPurple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hsl(292, 16%, 49%)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;darkPurple&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hsl(292, 42%, 14%)&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;images&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./assets/images/background-pattern.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;iconStar&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./assets/images/icon-star.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;iconPlus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./assets/images/icon-plus.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;iconMinus&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./assets/images/icon-minus.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;regular&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WorkSans-Regular&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;semibold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WorkSans-SemiBold&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;WorkSans-Bold&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;FAQs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;What is Frontend Mentor, and how will it help me?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Frontend Mentor offers realistic coding challenges to help developers improve their frontend coding skills with projects in HTML, CSS, and JavaScript. It's suitable for all levels and ideal for portfolio building.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Is Frontend Mentor free?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Yes, Frontend Mentor offers both free and premium coding challenges, with the free option providing access to a range of projects suitable for all skill levels.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Can I use Frontend Mentor projects in my portfolio?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Yes, you can use projects completed on Frontend Mentor in your portfolio. It's an excellent way to showcase your skills to potential employers!&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;How can I get help if I'm stuck on a challenge?&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The best place to get help is inside Frontend Mentor's Discord community. There's a help channel where you can ask questions and seek support from other community members.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id="layout"&gt;Layout&lt;/h3&gt;

&lt;p&gt;The first step of making an accordion is to make a simple list with all elements all expanded out. Which in code looks something like:&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StatusBar&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"light"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backgroundImage&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;background&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollContainer&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollView&lt;/span&gt;
          &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scrollView&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;showsVerticalScrollIndicator&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentHeader&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iconStar&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contentHeaderTitle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;
                &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
                &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
                  &lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="na"&gt;borderBottomWidth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                      &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
              &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cellHeader&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cellTitle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cellIcon&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iconMinus&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cellBody&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lightPink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;backgroundImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;100%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;resizeMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;cover&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;scrollContainer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;absolute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;white&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;borderRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;window&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;window&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;scrollView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;transparent&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;contentHeader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;flexDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;row&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;gap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paddingVertical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;contentHeaderTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fontFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;paddingVertical&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;borderBottomColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lightPink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cellHeader&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;flexDirection&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;row&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space-between&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cellTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fontFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;semibold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;semibold&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;90%&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;cellBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;fontSize&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;fontFamily&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fonts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;regular&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;lineHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;colors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grayishPurple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;paddingTop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The interesting bits are with creating a z-stack layout, with an image view in the background and a scroll view in front.&lt;/p&gt;

&lt;p&gt;The tricky thing with &lt;code class="language-plaintext highlighter-rouge"&gt;ScrollView&lt;/code&gt; is that it needs a fixed frame and flexible content size. And what this actually means in ReactNative is that the parent of &lt;code class="language-plaintext highlighter-rouge"&gt;ScrollView&lt;/code&gt; should have a fixed size and then the children can be whatever.&lt;/p&gt;

&lt;p&gt;But the container view also needs a &lt;code class="language-plaintext highlighter-rouge"&gt;position: absolute&lt;/code&gt; so we can move it in front of the image. And this bit of css does exactly all of that:&lt;/p&gt;

&lt;div class="language-css highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;  &lt;span class="nt"&gt;scrollContainer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;"absolute"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;"window"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dimensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;"window"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="n"&gt;-&lt;/span&gt; &lt;span class="m"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;top&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;16&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;//&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’re coming from a UIKit background like me and if you squint your eyes a bit this math looks like what you need to do for &lt;code class="language-plaintext highlighter-rouge"&gt;CGRect&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And with our basic setup is done&lt;/p&gt;

&lt;p&gt;&lt;img alt="Basic Setup" src="/assets/accordion-reactnative/basic.gif" /&gt;&lt;/p&gt;

&lt;h3 id="state-management"&gt;State management&lt;/h3&gt;
&lt;p&gt;Next step in making an accordion is to handle the state. And by state I mean either the selected index or an array of indices.&lt;/p&gt;

&lt;p&gt;In code this means to first introduce &lt;code class="language-plaintext highlighter-rouge"&gt;useState&lt;/code&gt; and then using the &lt;code class="language-plaintext highlighter-rouge"&gt;Pressable&lt;/code&gt; to update the state. And finally the UI to consume the state.&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;selectedIndex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setSelectedIndex&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt;
      &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cellHeader&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setSelectedIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cellTitle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Image&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cellIcon&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;selectedIndex&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;
            &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iconMinus&lt;/span&gt;
            &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iconPlus&lt;/span&gt;
        &lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;selectedIndex&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cellBody&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)}&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Basic Setup" src="/assets/accordion-reactnative/state.gif" /&gt;&lt;/p&gt;

&lt;h3 id="animate-changes"&gt;Animate changes&lt;/h3&gt;
&lt;p&gt;And the final step is to animate the changes. In ReactNative that means using Reanimated to animate the height.&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isOpen&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cellContainerStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useAnimatedStyle&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isOpen&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;withTiming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;withTiming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;cellContainerStyle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cellBody&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Cell&lt;/span&gt; 
  &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; 
  &lt;span class="na"&gt;isOpen&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;selectedIndex&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now to calculate the actual expanded height, we need to first calculate the max height of the &lt;code class="language-plaintext highlighter-rouge"&gt;Cell&lt;/code&gt; by wrapping the text within a &lt;code class="language-plaintext highlighter-rouge"&gt;View&lt;/code&gt; with &lt;code class="language-plaintext highlighter-rouge"&gt;position: absolute&lt;/code&gt; and using the &lt;code class="language-plaintext highlighter-rouge"&gt;onLayout&lt;/code&gt; callback to record the evaluated height.&lt;/p&gt;

&lt;p&gt;Then, we need to make sure the &lt;code class="language-plaintext highlighter-rouge"&gt;Animated.View&lt;/code&gt; has &lt;code class="language-plaintext highlighter-rouge"&gt;overflow: hidden&lt;/code&gt; to clip the content from being rendered outside of the box.&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isOpen&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setMaxHeight&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cellContainerStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useAnimatedStyle&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;isOpen&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;withTiming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;withTiming&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="nx"&gt;cellContainerStyle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; 
      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;overflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;
        &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;
          &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;absolute&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onLayout&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;maxHeight&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;setMaxHeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nativeEvent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;layout&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
          &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cellBody&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="Animation" src="/assets/accordion-reactnative/animation.gif" /&gt;&lt;/p&gt;

&lt;p&gt;And this is how you build an accordion with ReactNative.&lt;/p&gt;

&lt;p&gt;The code from this experiment is available on &lt;a href="https://github.com/chunkyguy/frontendmentor/blob/main/faq-accordion/app/App.js"&gt;https://github.com/chunkyguy/frontendmentor&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="reference"&gt;Reference&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://reactnative.dev/docs/view#onlayout"&gt;https://reactnative.dev/docs/view#onlayout&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://youtu.be/NzrJJLSbWf8?si=W1uXuSHyIWVEeWWE"&gt;Animated Collapsible Cards in React Native - Easier Than You Think&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://docs.swmansion.com/react-native-reanimated/examples/accordion/"&gt;Reanimated Example: Accordion&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Thu, 17 Oct 2024 22:30:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/js/reactnative/ui/2024/10/17/how-to-make-an-accordion-with-reactnative/</guid></item><item><title>The So-Called ESV</title><link>https://honza.pokorny.ca/2024/10/the-so-called-esv/</link><description>&lt;figure&gt;&lt;img src="https://honza.pokorny.ca/images/esv-heirloom-psalms.jpg" /&gt;
&lt;/figure&gt;

&lt;p&gt;A speech by &lt;a href="https://www.youtube.com/watch?v=NPUI5803gKc"&gt;Theodore Letis&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I want to start off by telling you a little story that happened to me while I
was in Berlin last week.  I was there attending an academic conference at an
ancient German university there, the International Meeting of the Society of
Biblical Literature, and I was addressing a session dealing with biblical
manuscripts.  Before I got to the meeting, I had to check into a very small, a
obscure little hotel in what used to be East Berlin because of economic reasons I
really couldn&amp;rsquo;t afford to stay in the big hotel with the big boys.  Turns out I
didn&amp;rsquo;t mind that at all because upon checking in, I met one of the most
interesting people I&amp;rsquo;ve met in a long time.  The desk clerk was a young Jewish
German girl who, when I told her that my profession was as an historian, she
just seemed to open up like a flower and began to talk at a very rapid rate of
speed with a great deal of emotion and sincerity, and started telling me about
the history of her family in Berlin.  And I immediately perked up my ears
because modern history is not really my specialty, but oral history is something
I&amp;rsquo;ve done a great deal with, and this is what this woman was giving me.  And she
pointed out the window and she said, that house around the corner there that you
can barely see, that used to belong to my relatives.  And in the late 1930s,
Hitler ran my family out of their home, and he took over their property.  And
she said, and then after the war, the Marxist Russians moved into the city, and
then they took possession of her family&amp;rsquo;s property.  And she said, it&amp;rsquo;s only now
in the 21st century that her family is beginning to sort out all the details
with the present German government to have to reclaim what was rightly her
family&amp;rsquo;s property.  One thing to read about something like that in a book, but
to speak to somebody to whom it happened, and to have to point out the property
to you.  And the very locale word took place brings an immediacy of that
historical experience into your own point of reference that&amp;rsquo;s unparalleled.  And
I thought about that the whole trip, and I was very thankful that I stayed in
that very obscure hostel in what used to be East Berlin as a result of that
exchange.  And I thought as I was coming here this morning that it&amp;rsquo;s a kind of
apt metaphor for what I want to talk about.  Because most of us who were born in
the 20th century have lived during a period of most unprecedented transition
where the Bible went from being the legitimate possession of the church, broadly
speaking, collectively speaking.&lt;/p&gt;
&lt;h2 id="commercial-interests"&gt;Commercial interests&lt;/h2&gt;
&lt;p&gt;The Bible went from being in the possession of the church as her birthright, as
her covenant document, into the hands of commercial landlords. It was a very
subtle movement that began at the turn of the 20th century and is now in
complete full swing. And just as much as that German Jewish girl&amp;rsquo;s family had
her property taken away from them and repossessed by illegitimate landowners,
the Bible now has been illegitimately taken out of the church and is in the
possession of commercial interest, particularly the English Bible. As a matter
of fact, there is no parallel for this anywhere else in the world to speak of.
It is a uniquely, not even a British phenomenon, it is a uniquely American
phenomenon. And the reason that this has taken place is because American
Christians have more disposable income than they know what to do with. And they
have no sense of perspective on their disposable income, disposable income,
because they don&amp;rsquo;t know how the rest of the world lives. They may watch a
National Geographic program on television now and again, they may watch a
history program, an anthropological study on television, or something, or read
a National Geographic, but that&amp;rsquo;s as close as they come, most of them, to
understanding how the Christian world operates in the rest of the world. And
one of the results is that Christianity in America is held up to scorn and
ridicule by the secular media.&lt;/p&gt;
&lt;p&gt;Let me give you two examples. I don&amp;rsquo;t think that there are two more popular
publications in America than Time Magazine and USA Today. This is a cover story
from USA Today that came out in 1998. The title of it is The Bible Business.
And the first line in the article says, for those who wonder whether the Bible
has an answer to every question, there soon may be a Bible for every
questioner. And on that cynical note, the article goes on to talk about the
commercialization of Scripture and the multi-million dollars that are involved
in the many, many, many, many different editions that are now being promoted
and publicized. A year later, the theme was taken up again in Time Magazine,
May of 1999, with a pithy little comment, again, very cynical. His way, your
way. And it goes on to delineate the fact that there&amp;rsquo;s now a custom-fitted,
tailored Bible for every niche of American society. Again, the article begins
very cynically. In the beginning was the word, but these days it doesn&amp;rsquo;t seem
to be enough.&lt;/p&gt;
&lt;p&gt;And so, you see, we might be very comfortable with this situation. We may have
been convinced by overwhelming advertising slogans and our friends and pastors
and TV personalities and radio personalities that this situation is not bad.
It&amp;rsquo;s actually good that there are so many possibilities in understanding the
word of God in the English language. But I can guarantee you that the secular
world has a better perspective on it. And when Jesus said that the lies of this
world are wiser than the elect, I think this comes close to what he had in
mind. That sets the situation.&lt;/p&gt;
&lt;h2 id="how-we-got-here"&gt;How we got here&lt;/h2&gt;
&lt;p&gt;But what I&amp;rsquo;d like to do in a little bit of time I have here is give you a
really quick panoramic sketch as to how we got here in the American church. How
did we get to this situation?&lt;/p&gt;
&lt;h3 id="early-church"&gt;Early church&lt;/h3&gt;
&lt;p&gt;I want to start by talking about the early
church. And I like what our brother had to say about the fact that Jesus didn&amp;rsquo;t
hang around with the religious establishment. He went to the rank and file. He
went to the streets. The poor heard him gladly, Scripture says, because he was
offering them the keys to the kingdom of God. And if you&amp;rsquo;ve got nothing, you&amp;rsquo;ve
got nothing to lose. And you&amp;rsquo;ve got everything to gain. And they heard him
gladly, whereas the established religious orders did not. When he rose from the
dead and ascended into heaven, and he did, by the way, in spite of the fact
that the New American Standard in the book of Luke omits that detail in its
earlier editions, they censor corrected it. But when the New American Standard
came out initially and for at least 20 years, if you turn to the book of Luke,
there was no ascension. They left it out. But in spite of that, he did ascend.
He promised his disciples, his followers, that it was very important for him to
leave because if he didn&amp;rsquo;t go, he couldn&amp;rsquo;t send the comfort of the Holy Spirit.
He promised that when the Holy Spirit was poured out would lead the church into
all truth. And that is a promise that is absolutely assured as Christ promised
that he will come back literally in the clouds as he left. The church has not
failed to be led installably by the Holy Spirit right up to this moment in
time. In spite of the fact of waywardness, in spite of the fact of error, in
spite of the fact of hirelings and false shepherds coming into the church, the
church has not failed to follow the path that the Holy Spirit has led her right
up to this moment in time. And one of the assurances that we have that this has
taken place is the fact that the Holy Spirit led the apostolic community to
compile the actual words and the actual event of the life of Christ and put it
in written form.&lt;/p&gt;
&lt;h3 id="first-three-centuries"&gt;First three centuries&lt;/h3&gt;
&lt;p&gt;And for nearly 300 years, martyr after martyr after martyr shed their blood to
make certain that the unbroken transmission of that account was kept in
violence and was transmitted with all fidelity and all accuracy. So much so
that Tertullian said it was the very blood of the saints of the martyrs that
was the seed of the church. The more they killed the Christian believers, the
more the church multiplied. And the more that those doctrines were believed
when they were read in various gatherings and congregations, right up until
about the fourth century. Now, during this time, however, there were all kinds
of attempts to corrupt the text, the text that recorded to us the life of Jesus
and his teachings. Church historian Eusebius tells us that a man by the name of
Marcion, decimated the entire New Testament when he got a hold of certain of
the manuscripts and didn&amp;rsquo;t accept any of the Gospels except for Luke, and Luke,
he greatly abbreviated. That&amp;rsquo;s something we know that took place and we know
that there were other heretical groups that when they got their hands on the
Christian manuscript, the Christian sources, they altered them, they changed
them, they added things. And when the Gnostic came along, they were so enamored
with the success of Christianity that they tried to emulate their books, their
sacred texts, their canon. And so that you have any number of Gnostic books
that were composed during these first 300 years. Here&amp;rsquo;s a book dealing with the
so-called Gnostic Gospels, the Gospel of Peter, the Gospel of Thomas. There&amp;rsquo;s a
whole collection of them in the Nag Hammadi Library that was found last
century, mid-century. You can check them out of the library and read them.
They&amp;rsquo;re nothing like Scripture, but they have the apostles&amp;rsquo; names attached to
them. They&amp;rsquo;re all pseudoprography because of the false book, but it&amp;rsquo;s another
form of an attempt to corrupt and lead the church astray. So you have heretics
changing the correct text and altering it. You have heretics creating
additional texts, trying to confuse and trying to lead astray. Not unlike what
happened here in the United States, once it became founded as a country when
you had various cults like the Jehovah&amp;rsquo;s Witnesses coming along, the Mormons
coming along and creating their own texts, their own Bible, the Book of
Mormons. It&amp;rsquo;s the same principle, cyclical, happening over and over again.
Nothing new here.&lt;/p&gt;
&lt;p&gt;But by the fourth century, something very, very important happened. The church
ceased to be persecuted and the Roman state finally recognized Christianity.
And three important things happened.&lt;/p&gt;
&lt;p&gt;Number one, the canon of Scripture was defined for the New Testament. The Old
Testament as well, but the New Testament is what I have in focus at the moment.
The 27 books that we know were legitimized, ratified, and canonized by the
fourth century so that there was no longer any dispute. If there had been prior
to that and there was a fluidity to the canon up to then, that fluidity was
passed, the canon was defined once and forever. 27 books, no more, no less.&lt;/p&gt;
&lt;p&gt;The Council of Nicea met in the pressure cooker situation of the debate with
the Aryans who challenged whether Jesus Christ was fully God or whether he was
created and derived from God, God in some sense, but not completely fully God.
A diminished kind of God. At the Council of Nicea, it was agreed upon. The
church collectively said Jesus Christ is not only God, but absolute God,
co-equal with the Father, full deity, and full humanity equally. Christ was
defined once and for all that silenced the Aryans, that silenced the Gnostics,
that aesthetic, that silenced every Christological error that had fermented 300
years prior to the Council of Nicea.&lt;/p&gt;
&lt;p&gt;The third thing that took place in the fourth century at the cessation of
persecution was the defining and the establishing of what was the correct text
of the New Testament. Keep in mind that the canon was defined, 27 books, in
Athanasius&amp;rsquo; Easter letter, but the text of Scripture, the canonical form of
each book was also ratified at this time. And how do we know that? We know that
because from this period on, from the fourth century forward, with the
cessation of persecution, one form of text emerged, one canonical form of every
canonical book emerged, and was read in the churches of Asia Minor, read in the
Greek Orthodox Church and throughout Byzantium for over 1500 years, one form of
text. Every other rival form of text was marginalized, pushed to the side, was
not reproduced in the Scriptoria throughout the Eastern Empire. Remnants of
earlier experimental texts existed or preserved, but they were not copied in
the ecclesiastical Scriptoria. One form of canonical text of the canonical
books of the New Testament was perpetuated, and that text is found in the
lectionary literature of Eastern Orthodox Church. It&amp;rsquo;s a well-defined text. In
the Western Church, a very similar thing took place, the Rome commissioned by
Pope Damascus, put together the Volgata Latina, the Latin Bible, and it was
very, very similar to the Bible of the Eastern Orthodox Church. And that became
the definitive canonical text in Latin for the Western Orthodox Church.&lt;/p&gt;
&lt;h3 id="the-19th-century"&gt;The 19th century&lt;/h3&gt;
&lt;p&gt;Now let&amp;rsquo;s move things up very quickly and bring it up to the 19th century. In
the 19th century, just as some of these Gnostic books were found in the 20th
century, in the 19th century, certain of these earlier experimental edited
manuscripts that were produced prior to the 4th century were discovered.&lt;/p&gt;
&lt;p&gt;One was found by Kant Von Tischendorf at a monastery at the foot of Mount
Sinai, St. Catherine&amp;rsquo;s Monastery. Another was found in the Vatican Library,
which folks had known about really since the 16th century. Even Erasmus knew
about it. He had over 300 readings from that manuscript given to him by an
associate working in the Vatican Library. They knew about the Vatican
manuscript, but nobody followed it, nobody copied it, nobody regarded it as
significant because it differed so radically from the ecclesiastical text, the
canonical text that the Eastern Church had used from the very beginning of the
4th century. But a handful of scholars, some of them English, early on,
however, early in the 19th century, German, Grisbach, looked at these two old
and radical manuscripts and recognized that in the oldest gospel there was no
resurrection account. And when they realized there was no resurrection account
in the Gospel of Mark in these two old manuscripts, they decided that on that
basis alone they must be seen as more authoritative. You probably wonder why
did they think that? Why did they think because they were lacking the
resurrection they should be more authoritative? Well, there was an incipient
belief in the more liberal wings of the church in the 19th century, really
going back to the 18th century during the English Enlightenment when the deists
were running rampant all over England, that the supernatural Christ was
basically created by the imagination of the church. Certainly he lived,
certainly probably was a prophet, but the attending miracles and his
resurrection was something that was an addendum, something that was added, an
expansion added to who Jesus was on the part of the church. Now, finally
scholars had evidence that this probably was the case because the two oldest
manuscripts that had ever been discovered in the oldest gospel account, Mark,
were lacking the resurrection. So this was proof positive to them that this was
the earliest account of who Jesus was before there was an afterthought. Yes,
certainly the resurrection still was found in some of the other Gospels, but
they were later Gospels. Mark was the earliest and if Mark lacked that ending,
that was tangible proof that it may well not have been there originally, that
it was an afterthought appended on. So there was all kinds of discussion and
debate, heated debate as to whether these manuscripts could now be used to
produce an updated English Bible. And by the year 1881, just such an update
took place, based primarily on these two manuscripts. There were a lot of
conservative churchmen in England who resisted the impulse to do this, but for
a hundred years it had been debated and finally the church of England, the
climate was right and they caved, and they produced a new English Bible based
on a new Greek text founded on these two fourth century manuscripts, lacking
the resurrection account in the book of Mark. Now, as you can well imagine, the
rank and file of Christians in Great Britain didn&amp;rsquo;t buy this Bible. I mean, a
lot of people purchased it as a novelty, but they didn&amp;rsquo;t buy it spiritually.
They didn&amp;rsquo;t accept it as authoritative, as substitutions of the Bible that the
church of England had used since the 17th century, so-called Authorized
Version, commissioned by King James.&lt;/p&gt;
&lt;h3 id="american-developments"&gt;American developments&lt;/h3&gt;
&lt;p&gt;But a counterpart was also produced here in America in 1901. It was called the
American Standard Version because it was the American counterpart to this
revised version that came out in Britain in 1881 in the New Testament. Again,
the ASV, nobody took it seriously. It did not enter the purpose of the majority
of the churches. Mostly the most progressive churches used it. The more liberal
leaning churches used it. But the rank and file of the conservative,
confessional Christians in America, whether they were Lutheran, whether they
were Presbyterian, whether they were Baptist, whether they were Anglican, did
not accept the RV, did not accept the ASV. But 50 years later, in 1946, what
would become, eventually, the National Council of Churches tried one more
attempt to put this critical text into the pulpits and into the hands of the
rank and file conservatives and mainline churches in America, and so produced
what came to be known as the revised Standard Version. I have a copy of it
right here. The original revised Standard Version, the entire Bible, 1952. The
New Testament came out in 1956. This is it. And true to form, if you can get a
copy of this in a library someplace, if you&amp;rsquo;ll turn to the end of the Gospel of
Mark, I&amp;rsquo;m not going to bother holding this up because you won&amp;rsquo;t be able to see
it from there. You&amp;rsquo;ll have to take my word for it. But it ends at verse 9. The
last 12 verses, dealing with the resurrection and the ascension, are reduced to
the most minuscule size, italicized print at the bottom of the page in a
footnote, almost illegible. I certainly can&amp;rsquo;t read it from here. And so the
Gospel in the English Bible now is without a resurrection account at the end of
the oldest Gospel. That, of course, is probably the most radical aspect of the
revised Standard Version, but it&amp;rsquo;s not the one that caused the most difficulty
or the most trouble. What caused the most difficulty was in Isaiah 714, where
the committee that worked on this Bible decided that the Hebrew did not mean,
behold, a virgin shall conceive, but rather it meant, behold, a young maiden
shall conceive. In spite of the fact that Matthew interprets that same passage
explicitly as virgin, the Septuagint Greek translation of the Old Testament
uses a Greek word that is absolutely explicit, Parthenos, which means virgin.
It doesn&amp;rsquo;t mean young maiden. But that ended up making this probably the most
radical and most exceptional Bible that had ever been produced in the English
language.&lt;/p&gt;
&lt;p&gt;And as a result, it&amp;rsquo;s sort of a tremendous amount of controversy and resistance
from the most fundamentalistic branches of American Christianity. There&amp;rsquo;s even
this wonderfully notorious story of one Baptist preacher holding it up in his
pulpit, taking a blow towards to it, and burning it in front of his
congregation. I certainly can appreciate his sentiment there.&lt;/p&gt;
&lt;p&gt;One of the most interesting and promising developments that took place on the
heels of the release of the New Testament portion of the RSV was the fact that
the most learned Old Testament professor in the United States at the time, who
also happened to be a confessional, Westminster Confession, Presbyterian
Calvinist by the name of Oswald Thompson Allis, wrote a book titled, and
perhaps you won&amp;rsquo;t be able to read this either, I had it blown up, but it&amp;rsquo;s
titled, &lt;em&gt;Revision Or New Translation?: a Revised Standard Version of 1946&lt;/em&gt;. And
here&amp;rsquo;s a picture of the venerable Oswald Thompson Allis. I want to tell you
very briefly a little bit about him, and I promise I won&amp;rsquo;t go long here. I&amp;rsquo;ll
wrap this up quickly. But O.T. Allis, as he&amp;rsquo;s better known as, he was born in
1880, died in 1973. This is a very good long life. He got his bachelor&amp;rsquo;s of
science from the University of Pennsylvania, Ivy League School. He got his M.A.
from Princeton in 1907, and then he went over to Berlin. He was one of the few
American scholars to earn a PhD at that point in American academic life. In
1913, he was awarded by the University of Berlin a PhD in Old Testament study.
That absolutely certified him as a colossal in terms of Old Testament study.
There was nobody who could stand shoulder to shoulder with OT Allis in terms of
his command of Old Testament study. Now in this book, as you can well imagine
from the two passages I cited to you, he came to this conclusion. He said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If by a liberal version is meant a version which represents a lax and liberal
attitude to the question of the plenary verbal inspiration and the divine
authority of scripture, then RSV is clearly such a version. Sufficient evidence
has been given in the preceding pages to show that it is governed by a very
different conception of what is meant by an accurate version, from that to be
found in the Authorized Version.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Well, that&amp;rsquo;s a pretty broad and sweeping statement to be made by a bona fide
authority with a PhD from Germany. He is not a rabble-rousing fundamentalist.
He&amp;rsquo;s a very careful and non-confrontational academic, and he writes a popular
treatise meant to be read by Lehman in his day, came out in 1948, as well as
academics, but it was meant to be read by Lehman like you when we were a more
literate population which would read things like this. And he&amp;rsquo;s very
forthright. He says, this is a liberal translation. And he gives you the reason
throughout the study, and hopefully our institute will be reprinting Allis'
book, because there&amp;rsquo;s been a development that has precipitated the need for
O.T. Allis&amp;rsquo;s judgment to be rediscovered again on the part of a conservative
Calvinist, because the very seminary that he taught at for most of his career,
he left Princeton in 1929 when Princeton went liberal, and with J. Gresham
Machen, he&amp;rsquo;d founded Westminster Theological Seminary, where I myself was a
student for four long years. And it was while he was there that he wrote this
treatise critiquing the RSV.&lt;/p&gt;
&lt;h2 id="the-esv"&gt;The ESV&lt;/h2&gt;
&lt;p&gt;Now, ironies of ironies, that institution, Westminster Theological Seminary,
has, from its faculty, somebody who is on the committee of a new Bible that has
just come out, titled The English Standard Version. Now, the irony that exists
here is that the English Standard Version is a Bible that has already been in
existence since 1946, or 1952, however you want to look at it. It used to be
called the revised Standard Version, but now it is called the English Standard
Version. It is the same Bible that O.T. Allis at Westminster Seminary gave an
unqualified thumbs down to. It has now been produced by faculty members from
Westminster Theological Seminary and is being promoted there very, very
heavily. Now, mind you, they have tidied it up, which legitimizes their
changing the title of it, so it&amp;rsquo;s no longer the Revised Standard Version.
Frankly, I think they should have been honest and called it the Evangelical
Revised Standard Version. That would have been a very honest thing to do, but
of course, marketing is key here, and that would not have been as marketable,
believe me, as calling it the English Standard Version. This is a bit
presumptuous, actually, because what they&amp;rsquo;re saying is they want this to be the
standard Bible in the English language, but if I have anything to do with it,
it won&amp;rsquo;t happen. But if you have any doubts about what I&amp;rsquo;m saying, if you look
on the copyright page, it says the Holy Bible, English Standard Version, is
adapted from the revised Standard Version of the Bible, Copyright Division of
Christian Education of the National Council of Churches of Christ in the United
States. Now, what that means is every time we take some of our disposable
income and buy one of these Bibles after being coerced by either one of our
friends, by a pastoral associate, by some advertising material we receive
through the mail, by some internet solicitation, some spam, every time we buy
an edition of this evangelicalized RSV, we are supporting the National Council
of Churches because they own the copyright to it. I held up to two clippings
from Time Magazine and USA Today where the Christian Church is being ridiculed
because they keep producing so many editions of the Bible in order to exploit
the financial revenue of the Christian church. They see how absurd it is, but
this irony is even more than I can handle. The fact that evangelicals, the
conservative, confessional Christians should be supporting financially the
National Council of Churches is beyond my &amp;amp;radar. I don&amp;rsquo;t know if you know
what the National Council of Churches believes, but it&amp;rsquo;s an aggregate of
churches that ordain women, think that homosexual lifestyle is normal, that
advocates the ordination of homosexual, that thinks that abortion is not
anything that&amp;rsquo;s prohibited in scripture, that&amp;rsquo;s the National Council of
Churches. Those are the people who receive revenue every time you purchase this
Bible.&lt;/p&gt;
&lt;p&gt;So you see we&amp;rsquo;re living in extraordinarily confusing and perilous times. In our
organization, we&amp;rsquo;re called the Institute for Renaissance and Reformation
Biblical Studies, and what we see ourselves as being for the church is a kind
of consumer&amp;rsquo;s advocate on these issues. There is no organization in the world
that I know of who will attempt to give you the kind of information that will
allow you to educate yourself on this kind of subject, so that you can learn
the facts about the history of the transmission of the Bible and not be
exploited by manipulative advertising slogans or multinational corporations.
And I think it&amp;rsquo;s time that we have a boycott and quit stroking the egos of
Christian faculty members who continuously work on these revision committees to
crank out Bible after Bible after Bible. It&amp;rsquo;s bringing confusion and chaos into
the church, and now it&amp;rsquo;s leading to the financial benefit and enhancement of
the National Council of Churches.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m right now, forthrightly, right here and now calling for a boycott. Don&amp;rsquo;t
spend any more of God&amp;rsquo;s money on another Bible. Read the one that you have.
Read it carefully, contemplatively, and then read UCB&amp;rsquo;s history of the
Christian church next to it, so you can find out what real Christianity was
about when people were being burnt alive in Nero&amp;rsquo;s garden, because they didn&amp;rsquo;t
want to relinquish copies of the Scripture and how far we are removed from that
in this day and age.&lt;/p&gt;
&lt;h2 id="closing"&gt;Closing&lt;/h2&gt;
&lt;p&gt;At any rate, I will close by saying I do have a table over there on the way to
the lunchroom with several books. This is a book that I&amp;rsquo;ve written on the
subject that I&amp;rsquo;ve been talking about, not just this Bible, but generally the
problem as it exists in the United States. The title of the book is &lt;em&gt;The
Ecclesiastical Text&lt;/em&gt;. I have a couple of other books, a little booklet here
called &lt;em&gt;A New Hearing for the Authorized Version&lt;/em&gt;, and I also have a postcard,
a little postcard that when you stop by the table, if you want to have a chat
with me, I&amp;rsquo;ll give you this beautiful postcard of Erasmus who gave us our first
Greek text, which has our website on it, and I would hope that if this is the
kind of subject that interests you, that you&amp;rsquo;ll come by and talk to me, and
even you young people out there, it might be that some of you are going to be
called by God to be a textual critic. Girl, boy, man, woman, you might be the
one that God will call to go and learn Hebrew, or go and learn Greek, and
possibly be another O.T. Allis in your day. And if you think that that might be
something that might be a possibility for you, you talk to me because we&amp;rsquo;d like
to encourage you in that direction. Thank you for your time, and I look forward
to talking to all of you who have some interest in this subject. Thanks.&lt;/p&gt;</description><author>Honza Pokorný</author><pubDate>Thu, 17 Oct 2024 22:00:00 GMT</pubDate><guid isPermaLink="true">https://honza.pokorny.ca/2024/10/the-so-called-esv/</guid></item><item><title>SwiftyCatalog</title><link>https://june.kim/swifty-catalog/</link><author>june.kim</author><pubDate>Thu, 17 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://june.kim/swifty-catalog/</guid></item><item><title>My Open-Source Chrome Extension for Notion Has Made Over $9K</title><link>https://gourav.io/blog/notion-boost</link><description>What started as chrome extension to solve my own issues has now made over $9K, all while remaining open-source.</description><author>Gourav Goyal</author><pubDate>Thu, 17 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gourav.io/blog/notion-boost</guid></item><item><title>How to build god and get away with it</title><link>https://taylor.town/build-god</link><description>Humanity will inevitably build god in man's own image.</description><author>taylor.town</author><pubDate>Thu, 17 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/build-god</guid></item><item><title>Shit may flow downhill, but responsibility flows uphill</title><link>https://www.nothingeasyaboutthis.com/shit-may-flow-downhill-but-responsibility-flows-uphill/</link><description>Bob moved to New York City to work for me. He was working for one of our largest customers, a large hedge fund. Two weeks in, he was fired. What do we take away from this?</description><author>Nothing Easy About This</author><pubDate>Wed, 16 Oct 2024 17:34:21 GMT</pubDate><guid isPermaLink="true">https://www.nothingeasyaboutthis.com/shit-may-flow-downhill-but-responsibility-flows-uphill/</guid></item><item><title>Journalists should not surrender their weapons</title><link>https://nicolaiarocci.com/journalists-should-not-surrender-their-weapons/</link><description>&lt;p&gt;Kara Swisher, a dean in digital and classical journalism, has an interesting article in the New York Magazine. As a witness and protagonist she recounts how in the last 30 years digital has eaten away at traditional media and how today, with the advent of AI, there is a risk of it happening all over again. Above all, she reasons why it is essential for journalists not to surrender their weapons and lawmakers to step in and finally harness an industry that always had free reign and no regulation, as it is considered inevitable.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Wed, 16 Oct 2024 11:24:37 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/journalists-should-not-surrender-their-weapons/</guid></item><item><title>I am not leaving AI</title><link>https://blog.separateconcerns.com/2024-10-16-not-leaving-ai.html</link><description>&lt;p&gt;I started my professional career almost 15 years ago &lt;a href="https://blog.separateconcerns.com/2013-06-20-three-years-proprietary-projects.html"&gt;working for a Machine Learning / Computer Vision company&lt;/a&gt; that did image recognition on mobile. At the end of 2013, I chose to leave it for &lt;a href="https://blog.separateconcerns.com/2018-01-06-four-years.html"&gt;Lima&lt;/a&gt;, in part because even at Moodstocks I was always more of a Distributed Systems / Algorithms person than a Deep Learning person.&lt;/p&gt;
&lt;p&gt;Now, after 10 years away from the field, &lt;a href="https://blog.separateconcerns.com/2023-03-31-joining-finegrain.html"&gt;I am back working on AI for images&lt;/a&gt;, this time more specifically on &lt;a href="https://finegrain.ai"&gt;automated and realistic image edition&lt;/a&gt;, with a part of the old Moodstocks team who spent years at Google after the acquisition.&lt;/p&gt;
&lt;p&gt;I did not make that choice lightly, I spent some time pondering it, because I knew it would require a significant time investment. AI is a complex, fast-moving field where not everything you learned elsewhere applies. Now that I am back, it is unlikely that I will leave it anytime soon.&lt;/p&gt;
&lt;p&gt;I got several interesting job offers recently, for Staff+ roles at great companies with classical distributed systems problems that would have appealed to me a few years ago. But it is likely that the real dist.sys fun in the next few years will be in AI, both &lt;a href="https://www.semianalysis.com/p/multi-datacenter-training-openais"&gt;for training&lt;/a&gt; and inference. The field has grown fast, and the tooling and practices could not keep up so there is a lot to build.&lt;/p&gt;
&lt;p&gt;Those who know me may also remember I always had a problem with the issue of interpretability / explainability of deep learning models, but in recent years I feel we have collectively improved on that, although there is still much to be done.&lt;/p&gt;
&lt;p&gt;In addition to that, I do not intend to leave Finegrain specifically either! We have a great team and we approach problems differently from most AI companies. We have products starting to ship, and a lot to do to improve them. I don’t think I should be anywhere else.&lt;/p&gt;
&lt;p&gt;Anyway, I am taking the end of the week off to attend and help with the &lt;a href="https://www.dotai.io"&gt;dotAI conference&lt;/a&gt;, and I will be back to work next Monday. In the meantime, &lt;a href="https://editor.finegrain.ai/signup"&gt;try the Finegrain Editor&lt;/a&gt; if you haven’t already :)&lt;/p&gt;</description><author>Separate Concerns</author><pubDate>Wed, 16 Oct 2024 11:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.separateconcerns.com/2024-10-16-not-leaving-ai.html</guid></item><item><title>Further thoughts on hobby photography</title><link>https://voussoir.net/writing/hobby_photography_2</link><description>&lt;article&gt;
&lt;p&gt;&lt;a href="/writing"&gt;Back to writing&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a class="tag_link" href="/writing/tags/photography"&gt;[photography]&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a class="tag_link" href="/writing/tags/introspection"&gt;[introspection]&lt;/a&gt;&lt;/p&gt;
&lt;h1&gt;Further thoughts on hobby photography&lt;/h1&gt;&lt;ol id="table_of_contents"&gt;Table of contents&lt;li&gt;&lt;a href="#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#edc"&gt;EDC&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#black_and_white"&gt;Black and white&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#subject_matter_subjects_matter_subject_matters"&gt;Subject matter / subjects matter / subject matters&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#originality"&gt;Originality&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#what_if_everyone_just_has_poor_taste"&gt;What if everyone just has poor taste&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#street_photography"&gt;Street photography&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#privileged_eye_outsider_eye"&gt;Privileged eye, outsider eye&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#videography"&gt;Videography&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#money_and_recognition"&gt;Money and recognition&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#zooming_out"&gt;Zooming out&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#website_portfolio_gallery"&gt;Website, portfolio, gallery&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#instxgrxm"&gt;Instxgrxm&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#how"&gt;how&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#fuji_dial_slipups"&gt;Fuji dial slip-ups&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;
&lt;h2 id="introduction"&gt;Introduction&lt;a class="header_anchor_link" href="#introduction"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It has been two years since I wrote down &lt;a href="/writing/hobby_photography"&gt;some early thoughts on hobby photography&lt;/a&gt; and I thought it would be worth following up as a separate article.&lt;/p&gt;
&lt;p&gt;This article contains &lt;a href="/writing/advertixing/#hailcorporate"&gt;brand names&lt;/a&gt; and I'm sorry about that. While there's plenty to say about the philosophy and ideals of photography in the abstract, I also want to give a few thoughts about the specific items I'm using.&lt;/p&gt;
&lt;h2 id="edc"&gt;EDC&lt;a class="header_anchor_link" href="#edc"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have not been able to convince myself to bring the Fuji camera everywhere. Although it is not humongous by any means, it is too large and conspicuous for me to go and do normal stuff without looking like I'm trying to be a "photographer" all the time, especially since it must be worn on a shoulder strap as I'm not in the habit of carrying a backpack, which seems to be a key factor amongst those who do carry these cameras daily. I want to increase the quality and quantity of life memories that I capture, but I don't want to feel like every day needs to be a photo shoot or like I'm playing a character. I want photography to be available to me whenever opportunity presents itself, but I don't want it to consume my entire personality and perception.&lt;/p&gt;
&lt;p&gt;So, I bought a Ricoh GR III.&lt;/p&gt;
&lt;p&gt;I told myself that even if I fail to EDC this one too, at least it can act as a B camera for when I've got the telephoto lens on my A camera, and indeed it has been a helpful addition there.&lt;/p&gt;
&lt;p&gt;I tried a couple different ways of carrying the GR, and wound up making my own little bag for it. This became &lt;a href="/writing/ricoh_gr_bag"&gt;its own article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2024-07-20_23-03-21.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I have been carrying it this way for a few months, even though I am constantly nervous that people will ask me "what is that you're wearing?". Surprisingly, this really has not happened. I wore it under my jacket back when the weather was cooler, and for some reason none of my coworkers, who would not be used to seeing this suspicious line of paracord, ever asked about it. Maybe they think it's a piece and if they ask too many questions I'll pop 'em.&lt;/p&gt;
&lt;p&gt;One of the final big pushes that got me to buy the GR was &lt;a href="https://youtube.com/watch?v=tI9qHg2yGtw" title="My First Digital Camera 24 Years Later! Intel PC Pocket Cam"&gt;this video&lt;/a&gt; by Clint of Lazy Game Reviews about an Intel pocket camera. Based on the pictures he shows here, it's clear he was in the habit of carrying this camera with him frequently, possibly every single day for some time.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/lgr_camera_1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/lgr_camera_3.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Most of the scenes he took pictures of are just ordinary, but that's what makes them interesting. After 24 years, even "ordinary" is different. A picture of a retail store shelf will make you say "wow, they used to look like that?" or "wow, they still look like that!". It's a winner either way.&lt;/p&gt;
&lt;p&gt;This video really made me feel like I've been missing out by not carrying a camera every day. A lot of times we think "I don't need to take this picture today, it can wait until tomorrow", but that will be a picture of tomorrow, not today. If you want a picture of today, this really is your only chance. By tomorrow you'll have missed it. So although I knew I would still deal with discomfort and embarrassment, I felt like I had to try.&lt;/p&gt;
&lt;p&gt;So I've been practicing a little bit of this lifestyle photography thing with some of my coworkers. Like when a group of us goes for a walk to stretch or we have a Thursday pizza party, I've taken a few casual pictures then. I keep expecting people to ask me questions about this camera, but they don't. I keep telling myself how abnormal it is to carry any kind of camera at all these days, and especially how abnormal it is to wear it on a homemade sling. Yet no one questions it. That's a good thing and all, but wow does it make me feel like the autistic one.&lt;/p&gt;
&lt;p&gt;Even still, I sometimes see pictures online of people working at their desk / cubicles in what seem to be candid or spontaneous circumstances, and I can't figure out how exactly those moments come about.&lt;/p&gt;
&lt;p&gt;I can feel that my mindset is still lagging behind the physical act of EDC. It is not enough to carry the camera if you do not use it. Last weekend I went out to see a band, and unfortunately the show got cancelled. I got to hang out with the band for about an hour anyway, and yet I didn't take even a single picture of them. It would have been so great to have a "We tried!" photo, and I can't believe this thought didn't cross my mind at the time. I just completely missed it. I still feel like I need some kind of clear event or scene to grant me permission to take photos — I need to get better at realizing the value of the present moment even if it is not what I came looking for.&lt;/p&gt;
&lt;p&gt;I said last time that I don't like PASM dials, but the GR is a PASM camera, so has my opinion changed? No. I still don't like PASM, and I consider it a downside of this purchase. This will spend most of its time in P anyway. In general, the shooting experience of the GR is a lot worse than that of a bigger camera. Even though it is simple, and it is a big step above a phone camera, I would not recommend it to photography beginners because it sacrifices a lot in the name of EDC. I miss my dials and focus ring. The on-screen preview always looks uglier than the photo, so you have to learn to ignore the preview and trust the camera. The blackout interval is so bad that in burst mode you literally don't know what you're getting after the first shot. The face-detection does not work in continuous autofocus, only single autofocus, which is weird since the face detection algorithm runs continuously during live view to present the white box in the first place. There are all around little touches to the firmware that could be improved — to give a hyper specific example, when the Fuji is in burst mode you can still chimp if you release the shutter button quickly enough, but if you keep it half-pressed it stays in live view mode, so you get the best of both single and burst shooting in a single mode. On the GR, it does not go back to live view when half-pressing the shutter in burst mode unless you turn off chimping entirely. I'd rather at least the chimp setting was stored separately across single and burst shooting.&lt;/p&gt;
&lt;p&gt;Maybe I didn't do enough research before buying, because the Sony RX100 VII apparently slashes a lot of these concerns with better continuous AF, no blackout time, a zoom lens, and even a flip-out screen, though with a smaller sensor to compromise. I can't say for sure I would have picked that if starting fresh, but darn. Of course, if there was such a thing as a Fuji X-T Mini I probably would have bought that.&lt;/p&gt;
&lt;p&gt;So far, I haven't addressed the elephant in the room, which is that my cell phone is already an everyday carry camera. Why not use that? I don't think I'll be able to give very convincing answers because it really is more of a psychological block than a practical one. I'll say first of all that I don't like the pictures my phone produces very much: they are simultaneously oversharpened and soupy despite what you'd think is a sufficient 12mp resolution.&lt;/p&gt;
&lt;p&gt;I don't want to buy a new phone just to prioritize cameras, because I feel like that's what almost everybody is doing these days and it has led to the severe atrophy of other good qualities and features: the 3.5mm headphone jack, the SD card slot, removable batteries, lay-flat back design, overall software UI and UX, cloud dependencies, and the squandering of local processing power. It's not that these things can't coexist with good cameras. They're all completely unrelated, technically. But I feel that the top producers of cell phones are able to abuse their customer from every direction and the customers put up with it because "the cameras are better than last time". I don't want to be stuck in the same cattle line.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/doing_my_part.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Hypocrisy alert: there are not many phones that still include a 3.5mm jack, so my next phone might be a Sony Xperia which happens to be definitively camera-centric. I don't want to get into the Chinese brands or Samsung so there aren't too many good choices.&lt;/p&gt;
&lt;h2 id="black_and_white"&gt;Black and white&lt;a class="header_anchor_link" href="#black_and_white"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I like black and white. There are many stereotypes about noobs / losers who think that black and white is a one-click cheat code to make any picture a masterpiece work of art. Am I one of those losers? It's possible. Personally, I think I actually like black and white, but of course that's exactly what one of the losers would say, wouldn't they.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The wife of a friend of mine decided one day that she was going to be a "professional photographer".&lt;/p&gt;
&lt;p&gt;She bought a cheap digital SLR and then she ran around taking random pictures of everyday bullshit, then spent a few minutes de-saturating them in PS or applying sepia-tone filters to them.&lt;/p&gt;
&lt;p&gt;She proudly displays them on her "professional photographer" website for all to see.&lt;/p&gt;
&lt;p&gt;What a disgrace to the profession.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://old.reddit.com/r/AskReddit/comments/gclsg/_/c1mnxwx/"&gt;https://old.reddit.com/r/AskReddit/comments/gclsg/_/c1mnxwx/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oops!&lt;/p&gt;
&lt;p&gt;I like black and white artistically because it accentuates geometry and texture. I like that it places distance between the photo and reality, allowing it to act as fiction. Once you get used to looking at black and white, you start to forget that anything is even missing at all, it just seems normal. Black and white pictures give off what is perhaps an illusion that they are sharper and more defined, which makes them more fun to zoom in and pixel peep and be amazed at what's resolved — every time I zoom in on a picture and see the individual threads of a person's shirt I am amazed. But I also like black and white pragmatically because at maximum ISO, black and white pictures look acceptably grainy but color pictures look hellishly bad. So when I'm shooting in the dark without a flash, I'll get better results in black and white.&lt;/p&gt;
&lt;p&gt;Oftentimes, I think I want to only shoot black and white forever. For the early decades of photography, that was the only option, and people got good results, so I probably could too. I feel like when I switch into color I am not being true to myself or my vision.&lt;/p&gt;
&lt;p&gt;When I shoot pictures that are going out to other people, though, I know that everybody else wants and expects color. I'm the resident event photographer at my workplace and it would be pretty dumb of me to send out pictures of a corporate event in black and white. A couple bands have paid me for my photos, and of course they want them in color.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;For a long time I was shooting both black and white and color, and trying to figure out how to mix them when I grouped photos together. It felt like two different photographers or maybe more like five different photographers.&lt;/p&gt;
&lt;p&gt;I took a workshop with Alex Webb and his wife Rebecca. They looked at the pictures and moved them around and whispered to each other, and then they both turned to me and Rebecca said "yeah, you don't really ever need to shoot color again the rest of your life".&lt;/p&gt;
&lt;p&gt;And I was like "thank you, so much". It was the greatest gift, I mean I still get chills when I think about it because it was such a defining moment. I became a black and white photographer in that moment and never looked back.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=2KMTzS1M1-M&amp;amp;t=3m15s"&gt;Reuben Radding&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Frankly, black and white is very often easier than color. Color is hard because it adds an extra dimension for failure, another opportunity to make things look bad. Even in good conditions, the colors of your subject might look bad against those of the background, or there'll be someone wearing a distracting neon-yellow sweater that ruins your shot. And in poor conditions you're fighting with the white balance or low-&lt;a href="https://en.wikipedia.org/wiki/Color_rendering_index"&gt;CRI&lt;/a&gt; indoor lights to keep people's skin from looking pale or sickly green.&lt;/p&gt;
&lt;p&gt;If color is hard, then black and white is a crutch, and to say "I will only shoot black and white forever" is to say "I'm not willing to take on a challenge and learn new skills". So it's probably important to do some portion of color photography pretty consistently, in order to be sure that the decision to use black and white is actually a decision and not a tar pit.&lt;/p&gt;
&lt;p&gt;Likewise, of course, color can also be a crutch. It's a people-pleaser, because the majority of people are normies content with "ooh, look at the pretty colors!". Color is an easy source of praise, so it can be difficult to stick to a black and white vision when others are involved. But remember also the difference between taking a beautiful picture and taking a picture of a beautiful thing; I often think to myself that if a scene doesn't look good in black and white then it's not a good scene, so there's nothing to lose by always shooting B&amp;amp;W. But I've never actually said that out loud because I don't have these kinds of conversations with anyone. One thing I know for certain is that if I'm shooting black and white and I see something very colorful so I switch the camera back to color, that'll be my worst photo of the day. "Look at the pretty colors" is the least convincing reason to shoot color.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/colors.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;When I shoot color, am I betraying myself to satisfy others? Yes. Is it good for me to betray myself, to force myself to try new things and learn? Yes. What does it mean to stay true to yourself, then?&lt;/p&gt;
&lt;p&gt;The fantasy of noble self-martyrdom tells me to reject everyone's expectation for color photos; to tell them that if they want color photos they'll have to take them themselves; to tell them that if they want to hire me they'd better be aware they're only getting black and white. The less-stupid side of me tells me that's stupid. If the people around me don't like the pictures I take or find it weird/uncomfortable that I insist on a black and white identity, then they won't want me around, and won't want me to take pictures of them, and then I'll just be alone with the camera, which defeats the point. It's only been two years, so it's too early to say I've found what I'm best at, yet. It's like a child watching the same movie on repeat dozens of times because they haven't discovered or can't process anything else yet.&lt;/p&gt;
&lt;p&gt;I took pictures at a 4th of July party in 2023 in black and white, and I regret it. I never sent the pictures out to anyone because I am embarrassed and I do not want to be confronted with the question "why are they in black and white?", since I don't have a reason. Noble self-martyrdom? It would be nice if I was at least personally satisfied with them, but I'm not. When I look at those photos, the only feeling they give me is that I was sticking dogmatically to the identity of black and white photographer as if that was important somehow. In 2024 I shot the same party in color.&lt;/p&gt;
&lt;p&gt;I think most of us assume that the general public doesn't like black and white, but for what it's worth, when I've shared my photos with strangers I've never gotten the sense that they were disappointed by it.&lt;/p&gt;
&lt;p&gt;I'll be leaving the GR in color mode by default. The purpose of the EDC is to capture life memories and documentary, which simply makes more sense in color. B&amp;amp;W might look better artistically, but it is pretentious to insist that every waking moment of life be a piece of art. Sometimes you want to remember something as it actually appeared. I think this will help me keep my use of black and white deliberate.&lt;/p&gt;
&lt;p&gt;I would be willing to buy a monochrome-only camera body, despite what I just said. My reasoning here has changed over time. I used to imagine myself buying a monochrome body to remove the option, temptation, and doubt to use color; to enforce the decision to shoot B&amp;amp;W at all times. So that when people ask me for the color copy of a photo I can tell them "ha, this camera can't even take color pictures". But after my experiences and reflections, I don't want that any more.&lt;/p&gt;
&lt;p&gt;I still would buy a monochrome body because when it's time for some deliberate black and white, a true monochrome sensor would feel like the right tool for the job. I would like to fully embrace the black and white power when the situation calls for it. Now, monochrome sensors do have a technical advantage in sharpness and low-light performance since there's no &lt;a href="https://en.wikipedia.org/wiki/Bayer_filter"&gt;bayer filter&lt;/a&gt; in front of the pixels, but I feel that's not actually a big enough point and only provides the guise of rationality to an otherwise lusty desire.&lt;/p&gt;
&lt;p&gt;Despite being a reduction in featureset, monochrome sensors of course cost more because the consumer demand is smaller. There are people out there who do monochrome conversions of color cameras by &lt;a href="https://www.youtube.com/watch?v=6PkXZMvzZHk"&gt;physically destroying the bayer filter&lt;/a&gt; off the sensor, but because the camera's firmware is still programmed to be a color camera, this essentially breaks the in-camera jpeg engine and requires all shots to be done in raw and processed on the computer. That ain't for me. If Fuji released a monochrome X-T body with all the dials and switches to my satisfaction (see below), it would pretty much be an instabuy for me at a price up to about $2,000.&lt;/p&gt;
&lt;p&gt;I must acknowledge the effect of gear on this so-called black and white identity, for a lot of things that we think are innate about ourselves are really just a product of circumstance. I think the fact that I did not start photography until the age of mirrorless cameras with EVFs is absolutely critical to my use of black and white. If I had started with a DSLR, looking through an optical viewfinder, I'm certain I would have taken all my pictures in color. And I mean that's exactly what happened when I borrowed my dad's DSLR at the rennaisance faire. And if I had grown up in the days of film, perhaps the time, effort, and cost would have kept me out of the hobby entirely.&lt;/p&gt;
&lt;h2 id="subject_matter_subjects_matter_subject_matters"&gt;Subject matter / subjects matter / subject matters&lt;a class="header_anchor_link" href="#subject_matter_subjects_matter_subject_matters"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I said last time that pictures of plants and flowers were not meaningful enough, and at this point I am staunchly against taking landscape and nature photos. I'm not saying you shouldn't, I'm just saying I don't want to. When I go for a hike I'll bring my camera, but I'll only take a few pictures to represent the day. I feel that 99% of people can't possibly care about a photo, like truly care beyond its moment of eyecandy, if it doesn't have a person in it. And before you say I'm submitting to people-pleasing, yeah, I'm part of that 99%. Documentary photos of places are still important historical artifacts that our society should continue to produce. But as an individual, while I might find place photos interesting to look at, they rarely evoke any deeper feelings. When they do, those feelings are usually envy that I don't live in a more interesting and beautiful place. So the emotional range of place photos is typically neutral to negative. Instead, I really focus on taking pictures of people and giving them away.&lt;/p&gt;
&lt;p&gt;For me there is the intrinsic satisfaction of creating art and memories that will last a long time, and the extrinsic satisfaction of giving people something they enjoy. To put it selfishly, I want people to like me? I use photography to get myself out of the house and into places I would not otherwise be, so that I can meet new people, have something with which to break the ice, and have a reason to keep them in my contacts. I do photography to socialize and connect. A lot of photographers say that they do it to "spread the love" or "tell our story" and... I don't really identify with this. When I talk about making connections, I don't mean it in the 1960s flower-power hippie &lt;em&gt;looove, man&lt;/em&gt; kind of way, more like the 2020s computer-science major "I don't have enough friends" kind of way.&lt;/p&gt;
&lt;p&gt;I no longer take photo walks through residential areas, either. It's just not interesting enough in a car-dependent place and the pictures do not mean anything to me. I do still take walks through the local colleges because at least the architecture and scenery is more interesting to look at. Still, the photos I take there usually aren't too great or meaningful. For me it is a chance to practice with different camera settings or styles I haven't tried before, so that I am more ready to use them in a real shoot. And every once in a while I come across something that might have lasting cultural value.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2024-03-24_12-10-31.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;By chance, I have spent a great deal of time in the genre of music photography. I personally don't play any music, and I never considered myself to be &lt;em&gt;that&lt;/em&gt; interested in live music. But music photography is available in a way that some other genres aren't. First of all, there's kind of a lot of people that play music. You can probably find at least one or a few people in your circle of family, friends, or acquaintances that play. Secondly, music provides a clear and obvious reason to take pictures of your subjects, whereas asking family, friends, or strangers to do posed studio or environmental portraits might feel embarrassing, fake, or forced. Music, like photography, intersects art and business — most people get into it for the love of the art, but also acknowledge the need to promote themselves in order to make the financials work out — so bands are glad to have pictures to use in their own promotion.&lt;/p&gt;
&lt;p&gt;I feel I am indebted to the city of Claremont for putting on the Friday Nights Live series of free outdoor concerts. I stumbled upon them in early August 2023, just days after &lt;a href="https://www.youtube.com/watch?v=xN4Ev8AfV3E"&gt;publicly ruminating&lt;/a&gt; over my lack of meaningful subjects to shoot. I decided to go for a walk through downtown after work, and heard the sound of music, and I've come back every concert since. I've met a lot of kind and encouraging people here, and it has brought me opportunities to shoot with some really great performers and receive positive feedback, not only from the bands but also from the regulars. On a few occasions, I have brought stacks of 4x6 prints of pictures I've taken of the dancers and given them to the dancers as gifts, and I've got some thank you cards in return.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/thankyou1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/thankyou2.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/thankyou3.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I made it my goal to attend every single Friday in 2024, and I stuck to that, but I don't think I will do the same in 2025. These last few weeks, my motivation to cull, edit, and publish has dropped like a stone. I think I am burnt out on shooting music for a while. Most of the bands in the Friday rotation I have seen two, three, or four times at this point. I don't want to keep shooting the same thing over and over again, and I don't think the bands need hundreds of pictures of themselves, either. I think I have hit a ceiling where my photos are not improving week to week and I am not learning. Partly this is because I am just an ordinary guest at these concerts, though a regular, with no particular authority or control. So when the band seats themselves in a way that is difficult to photograph, it is not my place to ask them to rearrange themselves, or pose, or move their lights and speakers out of the way. It's not a controlled shoot and while it's great to "challenge myself" looking for creative solutions to these problems, and the skills should be transferable to shooting in the real, uncontrolled world of event and street photography, it does put a limit on how good my photos can be that week.&lt;/p&gt;
&lt;p&gt;Furthermore, I want to spend more time in crowds a little closer to my own age, not to disparage the crowd here which has been so generous to me. I will have to look for a new way to use my Friday evenings, though I am not sure how to break the news to the dancers.&lt;/p&gt;
&lt;center&gt;* * *&lt;/center&gt;&lt;p&gt;A lot of my photos are not great. When I look back at the older ones, even and especially the orchestra concert that I said I was "very proud of" two years ago, I think a lot of them are downright terrible. I'll still give myself the excuses that it was a dark room and not under my control. If I was exclusively publishing my pictures online for the satisfaction and praise of random internet strangers, this would be a bit of a travesty. But because I prioritize sharing my photos directly with the subjects, I've quickly learned that even if my photos aren't great, it's possible they're the best they've ever had of themselves, and they'll tell me they love it. With the vast proliferation of cameras in the world, one might naturally assume that every single person has already been photographed to death, but the reality is you might be giving someone the best non-selfie photo they've ever had, and especially so for men. Sometimes I think about this and I worry it will make me complacent with mediocrity, but when I do look back at older pictures and I can see reasons they're bad, I know that means I must still be improving at least in taste if not in execution.&lt;/p&gt;
&lt;p&gt;It's okay to keep some bad photos, anyway, for a few reasons. First of all, and maybe this is just a coping mechanism, I'll say that it's exhausting when every single picture in an album is trying to be the crown jewel. Like if every photo looks like it's trying to wow you, it comes off as tryhard. In narrative media like books and film, the audience is given breaks between the setpieces, and a photo album needs that too.&lt;/p&gt;
&lt;p&gt;I think most photographers will agree that the real "wow" photos are rare; maybe one in a thousand or one in ten thousand in the age of burst shooting. I tend to think that my albums are a mixture of artwork and document. Yes I want to get some wows when I can, but I also want to document a place and event so people can see what it was like to be there. It's just not possible for every single one of those to be a wow, and that's ok because they still serve a purpose. It would be silly to delete everything but the wows and say yep, that's what I got out of tonight's performance: one picture, or maybe zero.&lt;/p&gt;
&lt;p&gt;I get some wide-angled establishing shots of the environment and key characters before going in for the closeups. I'm not very good yet at making these wides artistic (more on that later) but they too are critical to the album which would otherwise be heads floating in space. Some basic facts-of-the-matter boring pictures set the context for what comes. And finally, sometimes I keep a boring or bad picture because it might be all I got of a particular person and I feel it would be disrespectful to erase them entirely. When people look at the album, they'll say "oh hey there's Susan!" and it will make them happy even if it's not an expert photo. Obviously if it's really bad it might be worse than nothing, but that's a judgement call.&lt;/p&gt;
&lt;p&gt;In a music shoot, your favorite type of picture might be the lead singer belting out a big note, but I swear that you &lt;strong&gt;must&lt;/strong&gt; get some pictures of them with their mouth closed, too, or else you make them look like their jaw's stuck open. Even the pictures that aren't your favorite still play an important grounding role in the album.&lt;/p&gt;
&lt;p&gt;Yes, rules are meant to be broken. All that about composition, exposure, and focus are really just suggestions and you don't have to obey any of it. But nobody is unique, and it turns out that nobody is really even that different, and after 200 years of people taking pictures it's not surprising that we mostly agree certain things look good and others look bad.&lt;/p&gt;
&lt;p&gt;Of course it matters who you're taking pictures for. If it's just a job to pay the bills, then the client's opinion is all that matters. If it's just a hobby for yourself alone then it's the complete opposite. And in reality most of us will be somewhere in between, hoping to be liked and recognized but without selling our soul.&lt;/p&gt;
&lt;p&gt;Anyway, I think I actually do a good job, at least some of the time. I've got photos that still impress myself. I never feel like I'm doing perfectly, and that's partly because I keep getting myself into new types of shoots I've never done before. In that sense I am glad to be eternally amateur — it means I am not stagnating.&lt;/p&gt;
&lt;center&gt;* * *&lt;/center&gt;&lt;p&gt;When you read my blog articles, not only about photography but also about &lt;a href="/writing/obsessed_with_gpx"&gt;GPX recording&lt;/a&gt; and &lt;a href="/writing/notes_on_paper"&gt;document archiving&lt;/a&gt;, for example, you'll see a recurring theme of recording and capturing things to be enjoyed in the future. The word "memories" comes up a lot in the discussion of photography, in particular. We're all out here capturing memories, aren't we.&lt;/p&gt;
&lt;p&gt;I don't consider myself to be excessively nostalgic, and I really am not the type to earnestly use the phrase "the good old days", but if something is valuable and enjoyable today it'll probably be valuable and enjoyable in the future, too. I watch a lot of movies from the 70s and 80s not because I have nostalgia for a time I did not experience, but because the movies are well-made and still fun to watch — I'm sure there are just as many well-made movies happening today, they're just buried under a lot of junk and the great sieve of time needs to separate them out. But the fact of the matter is there are people I used to know and places I used to go that I cannot even picture anymore. I feel like I should do a better job capturing memories of these things.&lt;/p&gt;
&lt;p&gt;Photography is a tool for timeless, high-quality art just as much as it is for personal/family memories. A studio portrait with dramatic pose and lighting might rank highly on the art scale, but not so high on the memory scale except insofar as you remember the creative process that inspired or brought you to it. A moment captured at a birthday party or baby shower might not be so artistic, but could be cherished as a family memory worth framing on the wall.&lt;/p&gt;
&lt;p&gt;Other types of photography can fall elsewhere on these scales. When I shoot music I try to take photos that are good, artistically, but I also hope they evoke fun memories of a fun time, even if that means some technical misses. Street photography aims to be artistic while contributing to the history books of tomorrow with scenes of today. Contrast all this with product photography or promotional photography used in advertising, streamlined towards short-term financial interests, not carrying any emotional weight for anybody involved, except perhaps for the young fledgling model getting their first big break.&lt;/p&gt;
&lt;p&gt;How much does this focus on "memories" or "the future" indicate a dissatisfaction with, or insecurity about the present? Is it the case that the time I spend now, capturing for the future, is not fully enjoyed in the present? I'll admit that when I'm shooting music, I'm not really listening to the music, and I might not be able to tell you which song was my favorite after the set ends. And in the nebulous future when I am looking back, will I be failing to enjoy &lt;em&gt;that&lt;/em&gt; present for being too engrossed in the past? When exactly is the right time for all this? This is a lot of rhetorical exaggeration, of course — if we can waste a few hours today watching TV or scrolling the internet without an existential worry that we're failing to enjoy the present, then surely we can afford to spend a few minutes looking at an old photo. But as the stack of photos gets bigger, so too does the question.&lt;/p&gt;
&lt;center&gt;* * *&lt;/center&gt;&lt;p&gt;We visited my grandfather in the hospital about thirty six hours before he passed away. I brought my camera knowing this could be the last chance for any pictures. My cousin gave him a shave using a disposable razor and a bowl of soapy water. At his wake, it came up: "did anyone take a picture of that?".&lt;/p&gt;
&lt;p&gt;But I hadn't, and nobody else had either. I took a couple of pictures of the exterior of the hospital building on our way in, but I did not take a single picture inside the room for the two hours I was around. It's one thing to take a picture of someone with the thought of "you're going to die some day so I need to do this while I have the chance" echoing in my mind, but it's quite another when that person is literally about to die tomorrow and we both know it. He was still completely lucid and conversational, though struggling; he never did lose his mind. I imagined myself in his position: physically unable to perform a basic task and being made a photographic spectacle of so everybody can remember my moment of weakness for the rest of time, like a zoo animal for amusement.&lt;/p&gt;
&lt;p&gt;I'm sure a lot of family members would have been happy to see that picture. I'm sure that if it had brought him any embarrassment, shame, or feelings of mortal fear, those feelings would have been erased the next day anyway. Funerals are for the living, as is everything. But I am one of those living and the feelings of that moment would have persisted through me. I didn't think it was a worthwhile exchange. By writing it here I am preserving that moment in another way.&lt;/p&gt;
&lt;center&gt;* * *&lt;/center&gt;&lt;p&gt;In a lot of ways, photography is one of the "easy" arts. When an artist wants to make a picture in ink or paint, they have to sit down and actually &lt;strong&gt;make&lt;/strong&gt; it appear on the page. Like, by hand. And the musicians I shoot demonstrate great skill with their instruments and voice, these skills acquired through many hours of practice and hard work. All I do is find a neat place to stand or crouch and press a button.&lt;/p&gt;
&lt;p&gt;When I watch street photography videos I'll sometimes hear the phrase "making photographs", and I really dislike it. I know I'll sound like a jerk, but this phrase makes me think less of the person saying it. Especially in the context of street photography where most pictures are candid, and even the non-candid ones are only minimally posed from how they were found, pressing a button on a magic box for 1/250 of a second seems like an exceptionally low bar to be called "making" anything, and I feel it discounts the effort that goes into most other forms of making. I think the phrase "making a photograph" only sounds acceptable in a fully controlled environment where every element of the scene is deliberate. Otherwise, just admit the fact that you're taking what is given to you, and say "taking".&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2022-09-10_13-15-42.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I put a tally on the bottom of my portfolio that adds up the exposure time from all my pictures according to the EXIF data. With over 3,000 published photos, I'm coming up on just 40 seconds, and 10 of that was light painting long exposure.&lt;/p&gt;
&lt;p&gt;This tally serves two purposes. The first is the willful self-deprecation of photography as the easy art that doesn't take any effort, so that's out of the way. The second is a reflection on the strange feeling that I get when I think about the nature of photography, and how it locks in a particular millisecond out of the 86,400,000 milliseconds that occured that day to be preserved for all time. What made that one so special? The moments captured by a typical photograph are so brief that we truly have no other way to perceive them. When I am talking to someone face to face, I can see their gaze shift and their brow wrinkle and their mouth move and their hands gesture, but I never see the totality of their person at once because human vision is still limited by &lt;a href="https://en.wikipedia.org/wiki/Foveated_rendering"&gt;foveated rendering&lt;/a&gt;. When you look at a person, you never really see the whole person. But the camera captures the whole thing, all at once, for a level of scrutiny not available through any other means. It's pretty weird if you think about it.&lt;/p&gt;
&lt;h2 id="originality"&gt;Originality&lt;a class="header_anchor_link" href="#originality"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This section was drafted after I watched one of Paulie B's videos with Reuben Radding where they talked about &lt;a href="https://www.youtube.com/watch?v=MFSVsDrrkX8&amp;amp;t=9m14s" title="How to be original in street photography -- Office Hours with Reuben Radding"&gt;how to be original in street photography&lt;/a&gt;. I thought I would publish this as its own article, but let's just lump it in here. I like when these articles get long.&lt;/p&gt;
&lt;p&gt;There is a beautiful and illuminating irony in the act of submitting the question "how can I be original?" to a Q&amp;amp;A session like this. Surely once you're on the internet and you have a search bar under your fingertips, you're able to find hundreds of thousands of articles, youtube videos, think pieces, and motivational speeches on how to be original from people who think they're qualified to tell you. Instead, you choose to submit it into a Q&amp;amp;A for one particular artist to answer, forcing them to retread a territory so often trod before them, definitively unoriginal, not because their answer is the only one available to you, but because you respect that particular artist's opinion over the opinions of others, and hearing what &lt;em&gt;they&lt;/em&gt; think is more important to you than hearing what just &lt;em&gt;someone&lt;/em&gt; thinks. And now here I am being reached and influenced by it, instead of all the other opinions out there from other people that have never reached me. We've blown so far past full circle we're coming up on spherical.&lt;/p&gt;
&lt;p&gt;Turn this around on yourself and you'll see what I mean. You want to hear Reuben's opinions because you like him. So if someone likes you, they'll want to see your work and hear your opinions, and they will not care if you're being "original". Ta-da, it's that easy.&lt;/p&gt;
&lt;p&gt;The internet allows us instant access to many of the best works ever made in every category, and if you compare your work against the greatest of all time, of course you're going to feel like you're behind. But I'm going to keep hammering this same point over and over again: when you give the photos to the group of people that actually matters instead of trying to impress internet strangers, all that infinity of better works vanishes in a puff of smoke. The notion that Winnogrand took better pictures is preposterously irrelevant when you have the opportunity to give someone pictures of themselves and their friends, which no Winnogrand can do for them. How unoriginal!, they'll surely complain.&lt;/p&gt;
&lt;p&gt;There are eight &lt;a href="https://www.youtube.com/watch?v=HZmafy_v8g8"&gt;billion&lt;/a&gt; people in the world, and every second that passes in your life is eight billion seconds passing for everybody else. Everybody is missing out on 99.999% of everything that happens at all times. So even if you take a picture that you feel is trite and cliche, someone will say it's the first time they've seen anything like it.&lt;/p&gt;
&lt;center&gt;* * *&lt;/center&gt;&lt;p&gt;I'd like to specifically comment on the originality of travel and architecture photos. There is a certain kind of light mocking people get when they take a picture of a famous place. "Could have saved yourself the effort and taken one from google images, and it would look better too".&lt;/p&gt;
&lt;p&gt;The &lt;a href="/writing/hobby_photography_2/camera_restricta.html"&gt;Camera Restricta&lt;/a&gt; is an art/tech project that encourages creative photography by physically disallowing you from taking a picture in places where too many pictures have already been taken.&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The project is not only a piece about censorship in a political sense, but also questions our photographic practice. With digital photography displacing film, taking pictures has essentially become free, resulting in an infinite stream of imagery.&lt;/p&gt;
&lt;p&gt;Camera Restricta introduces new limitations to prevent an overflow of digital imagery. As a byproduct, these limitations also bring about new sensations like the thrill of being the first or last person to photograph a certain place.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The Camera Restricta is itself a creative work, and I'm not here to criticize it. They're not saying every camera should behave this way, but it's a deliberate limitation you can give yourself by wielding the Restricta. Taking on deliberate limitations is an important part of being an artist. But the idea that "too many people have taken this picture" and "this has been done before" is what I'm scrutinizing.&lt;/p&gt;
&lt;p&gt;Taking pictures of a place that has been "done before" is ok, because change over time is imperceptible. If you say that today is not different enough from yesterday to warrant a new picture; and tomorrow is not different enough from today, then you'll have to prescribe some minimum interval that you think is allowed — and I might disagree with you. If you wait for change to happen, you'll miss it.&lt;/p&gt;
&lt;p&gt;This kind of rule only works because the person issuing the rule knows that people will break it. Where the heck do you think the pictures on google images came from? They're not mocking the idea of &lt;em&gt;someone&lt;/em&gt; taking a place photo, they're just mocking the idea of you taking it. If everyone did follow the rule, and everyone diligently stopped taking pictures of the overdone place, there would come a day when the building is demolished and the most recent photos are decades old. For what benefit? Just a perception of originality, or not being a sheep.&lt;/p&gt;
&lt;p&gt;You may have heard the phrase "if you outlaw guns, only outlaws will have guns". The type of people who obey laws in general are probably more responsible, cooperative, social, and less likely to commit crime, but the type of people who commit crime don't care if guns are outlawed because they're already committing crime anyway. So then the responsible people are less able to defend themselves, is the point. I feel that the same kind of effect can apply to creativity as well. If there's a supposed rule about pictures you're not allowed to take, it is more likely that the thoughtful and considerate people will obey the rule while the self-centered "don't tell me what to do" types will ignore it. Which one would you rather see pictures from? As another example, if you convince all photographers that candid street photos are immoral and should be stopped, you'll be left with nothing but Bruce Gildens who don't care.&lt;/p&gt;
&lt;h2 id="what_if_everyone_just_has_poor_taste"&gt;What if everyone just has poor taste&lt;a class="header_anchor_link" href="#what_if_everyone_just_has_poor_taste"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Wizards With Guns made a sketch that gave me some long-lasting anxiety. &lt;a href="https://www.youtube.com/watch?v=AxE04GId49s"&gt;"I seduce the dragon"&lt;/a&gt; is about a group of DnD friends having a laugh before their game. It's a feast of hyper-wholesome bro-love moments that'll make your friendships look pallid by comparison. On one hand, it's nothing but great that bros can get together and reinforce one another. But on the other hand, the backbone of the entire sketch is that they're obviously super lame. Too easily impressed, too exaggerated in praise ("that belongs in a museum", "you should write a book", "you should open a bakery", "you should be an actor"), too deep into their uncool hobby. The characters are played with, to put it indiscreetly, speech patterns and mannerisms associated with being mentally retarded. There's no other way to say it. Since this is fiction and &lt;a href="https://youtube.com/watch?v=AxV8gAGmbtk" title="Folding Ideas - The Thermian Argument"&gt;everything in fiction is there because the authors wrote it on purpose&lt;/a&gt;, this suggests that Wizards With Guns believes positivity and uplifting compliments are correlated with being retarded. The characters in other WWG sketches don't talk like this, if that counts for anything. At the very least, we can be sure that positivity correlates with having poor taste.&lt;/p&gt;
&lt;p&gt;One of them busts out some drawings that blows the others' minds:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/wwg1.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/wwg2.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Is that me? I hope that's not me.&lt;/p&gt;
&lt;p&gt;The comments on the video are, rightfully, very positive themselves:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Need more sketches where it genuinely feels like the joke is on me. Watched 6 minutes of cringe goofballs and in the end was reminded that they don't care, they love each other, and they're happy. I'm the fool. 10/10&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;I'm almost glad there wasn't a punchline - these characters deserve to be happy&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;The whole time I had this sense of unease, "When are these harmless goofs gonna have the joke put on them?," but then it never came. Instead just humanity&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;20/10, I seriously expected a brutal dunking to occur, or for something to go wrong, but instead it’s bros being bros, and loving each other. It hits me right in my softest bones&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;These guys are true homies. They Gas each other up, rally, and encourage each others potential. This is beautiful.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;This was the most stressful video I've seen in awhile, I just kept waiting for the punchline and it never came. So wholesome it hurts&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Is it cringe if everything they said made me feel genuinely happy and nostlagic? thats the life man, thats the goal. Its a great phase in your life, being cringe is mandatory&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;&lt;p&gt;Even if this is super cringe it's also peak friendship goals. So much positivity.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Because of course we all wish we could be so unabashedly happy with ourselves. But I find these comments inherently patronizing. These comments only exist because the commenters enjoy the perspective of an external observer, and comfortably perceive the characters as being more lame than themselves. If the characters in the skit were a bunch of sexy people having a good time, nobody would feel the need to congratulate them on "not caring", because of course what are you implying there is to care about? It is only by virtue of the fact that lame people having fun is considered not normal, and that we imagine we'd be unhappy with ourselves if &lt;em&gt;we&lt;/em&gt; were that lame, that it is even worth mentioning. It's like looking at an ugly couple and saying "I'm so glad you found someone that thinks you're beautiful".&lt;/p&gt;
&lt;p&gt;Then the video ends, and you go back to your own life, and you assume that you are not that lame, and when you receive a compliment on your work you assume the giver is not retarded. But what if you're on the inside of the joke, and the external observers are patting you on the back, congratulating you on "not caring" despite how lame you are.&lt;/p&gt;
&lt;p&gt;The things I'm saying here are not very nice, so I hope you realize I'm writing this because I'm thinking about it and struggling with it, not because it's the right way to think.&lt;/p&gt;
&lt;p&gt;By the way, WWG is great. &lt;a href="https://www.youtube.com/watch?v=SAhTpJmzcog" title="TV shows where the detective has a cool gimmick"&gt;Here's my favorite video by them&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="street_photography"&gt;Street photography&lt;a class="header_anchor_link" href="#street_photography"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I am still interested in the genre of street photography. But, like I said last time, the area around me is not good for it, so I don't even try at home. I've been making trips to Venice Beach, where I serendipitously discovered the Sunday drum circle, and that's been where I've got a lot of my favorites.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2024-07-07_19-47-35.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2024-07-28_19-02-50.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Throughout this article you'll see that I'm mentioning &lt;a href="https://www.youtube.com/channel/UCAS6u29fObpCHYgHQCGaSUQ/videos"&gt;Paulie B's&lt;/a&gt; Walkie Talkie series a lot. On one hand you might say it's shallow or narrow-minded to rely so heavily on one source for influence, and I agree with that. There's more to street photography than the NYC Leica/Portra gang. But on the other hand, Walkie Talkie is probably the single best thing to happen to street photography in a long time and it deserves more recognition than I can give it.&lt;/p&gt;
&lt;p&gt;Looking at the older videos on Paulie B's channel, you can see he was mostly doing POV snap-and-dash street photography. With all due respect, I'm glad he's moved away from that. Walkie Talkie, documenting the thoughts, motivations, and inspirations of a community of artists, is so much more important and meaningful than collecting yet another photo of a random dude smoking a cigar.&lt;/p&gt;
&lt;p&gt;When we talk about the philosophy of street photography, we often say we are "finding beauty in the ordinary" or "capturing life's simple moments", or something like that. It sounds very pretty and noble, but I think it's worth asking if this is more than a bit disingenuous, considering that the hotspots for street photography as a genre are some of the most busy and exciting places in the world, NYC being the American epicenter, Tokyo another. In July 2023 I made a POV street photography parody video called &lt;a href="https://www.youtube.com/watch?v=xN4Ev8AfV3E"&gt;YTSPPOVVLOG #49&lt;/a&gt;. It's pretty hilarious if I do say so myself, but the humor comes from a place of deep personal dissatisfaction. You can't convince me there is beauty in this ordinary.&lt;/p&gt;
&lt;center&gt;* * *&lt;/center&gt;&lt;p&gt;Street photography is somewhere between art and documentary. Well, perhaps everything is. We're looking for compositions, expressions, and gestures that are artistically interesting to look at, but also capture the facts of the present that will distinguish the photo from future and past. The architecture, fashion, transportation, commerce, and public events of today will someday be studied as "the way things used to be".&lt;/p&gt;
&lt;p&gt;But documentary isn't really documentary if it only captures the pretty parts, is it? If you want to show "what life was like" at this time and place, you'll have to capture the bad parts too in order to do the story justice. Homelessness, drug addiction, conflict, hate.&lt;/p&gt;
&lt;p&gt;Everybody wants documentary to exist, but not everybody wants to experience it happening. We as the public want to be able to read about and see the hard times that happened in the past so that we can avoid them in the future, but nobody wants to personally be that face of homelessness or poverty or addiction; to be spotted at a low point in life, captured, and pasted up for everyone to see.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Migrant_Mother"&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/Lange-MigrantMother02.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Respectful and empathetic street photographers recognize that even with the legal right to photograph anybody in a public place, it's just not polite to take advantage of struggling people for the benefit of your "portfolio". We can say the same about people struggling with obesity, disability, or other physical differences.&lt;/p&gt;
&lt;p&gt;Does this lead to erasure? Erasure of homelessness, addiction, obesity, and the wheelchair-bound? Isn't it wrong to sweep under the rug everybody that may not enjoy being photographed, to preserve their fleeting feelings of shame at the expense of everlasting documentary authenticity, to falsely portray a world in which everybody is attractive and clean and well put together, instead of the real world?&lt;/p&gt;
&lt;p&gt;...probably not. This line of thought is tempting, but before you are hooked and reeled in, you should ask yourself if you're really just being a slacktivist. These days (and in the past, but &lt;a href="https://en.wikipedia.org/wiki/Mitch_Hedberg"&gt;these days, too&lt;/a&gt;) a lot of us want to feel like we're doing something important, but don't actually want to put a lot of effort into it. There are actual documentarians out there dedicating their full-time careers to researching, interviewing, working with organizations for access, and spending substantial amounts of time with their subjects before publishing their faces. You can watch a lifetime supply of these on &lt;a href="https://old.reddit.com/r/documentaries"&gt;/r/documentaries&lt;/a&gt; or &lt;a href="https://documentaryheaven.com/"&gt;documentaryheaven.com&lt;/a&gt; and elsewhere. Pointing your Fuji in all directions and pressing the magic button is an awfully low bar to call yourself a documentarian, and it's not exactly a great oppression to say that you should leave the more sensitive subjects to the people who are willing to put in the work and do it right. Despite what I said in the Originality section about doing your thing in spite of a saturated genre, it might actually be a good thing to stay out of the genres that take advantage of others' hard times.&lt;/p&gt;
&lt;center&gt;* * *&lt;/center&gt;&lt;p&gt;There are lots of arguments on the internet about whether a photo must be candid in order to be called "street photography". If you ever ask someone for permission to take their photo, it is no longer "street photography" and instead it is "street portraiture", or at least that's what people will tell you.&lt;/p&gt;
&lt;p&gt;To me this is a lot of pearl clutching. It is like arguing whether the word "guitar" inherently refers to an acoustic or an electric, and forcing the other group to specify that theirs is the non-default. A power play over language rather than an actual benefit to the art. A fight between two groups over who gets to own the implicit adjective. This whole argument goes away if you just specify "candid street photography" when it matters, and also if you recognize that it doesn't usually matter.&lt;/p&gt;
&lt;p&gt;When I do street, I like to be somewhere in between. I think some of my best pictures come from people that I was trying to get candid, but they noticed the camera and hammed it up.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2024-07-28_18-44-41.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Do you want to call these "street portraits" simply because they are technically not candid at that particular moment? I didn't give them any instructions and oftentimes I don't say anything at all — after I get the shot I put the camera down and smile, wave, or give a thumbs up, and usually that's the end of that unless they want to talk or exchange phone numbers to get a copy. They were already there doing their thing, and they shared a second of it with me. That counts as street photography as far as I'm concerned. All I'm saying is this terminology debate and dogmatic obsession with candidness is not helpful so let's just stop.&lt;/p&gt;
&lt;p&gt;When we talk about street photography, we spend a lot of time talking about people who don't want to have their picture taken, and how to be respectful about that. I myself don't enjoy being on the wrong side of the lens very much. With all of these preconceived notions coming from discourse on the internet about unwanted photography, it is a genuine surprise when, out of nowhere, a stranger calls out to have their picture taken (shown below), or when the basketball players say "keep the pictures going, we like that". So if you're interested in doing street photography, but don't want to come across as predatory, creepy, or invasive, you should give it a try and you might find plenty of willing subjects. You just won't come home with quite as many photos as the ones who snap and dash.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2022-08-28_19-07-20.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2022-09-05_17-58-37.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2024-05-26_15-58-24.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2024-08-08_14-08-59.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="privileged_eye_outsider_eye"&gt;Privileged eye, outsider eye&lt;a class="header_anchor_link" href="#privileged_eye_outsider_eye"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Taking pictures of people is easier when everyone is comfortable with the photographer's presence, and that comes from trust. Trust that through choice of angle, lighting, and shot selection, you'll show everyone looking good. When you're capturing individual milliseconds of someone's facial expressions, even a supermodel can be made to look bad, but it's ok because I won't make you look bad.&lt;/p&gt;
&lt;p&gt;I think it is worth reflecting on this privilege. I am the one that is allowed to see the derp faces, the sneezes, the unflattering angles of rear ends suspended in the motion of dance. I am the one with the magic box that I can point in any direction and freeze you doing whatever you're doing. I am allowed to spend as much time as I want looking at these moments, locked in time, that nobody else will ever see... And I delete them.&lt;/p&gt;
&lt;p&gt;Perhaps this is not such a meaningful privilege, since everybody knows that if you pause a video of a person talking you'll almost certainly freeze them looking derpy. By publishing a video of themselves speaking, the host has already granted every viewer the ability to see their weird intermediate faces.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/derpface.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;But I don't want to halt this train of thought just yet.&lt;/p&gt;
&lt;p&gt;For this reason, I have difficulty photographing people that I think might not be happy to see themselves in pictures at all, even if they don't say it out loud. Bluntly, I'm talking about people who are overweight or have a physical deformity or maybe just don't look that great for some other reason. These are the kinds of things you're not supposed to say because it's not polite. It would be great if I could say that I'm just capturing reality and I'm not here to judge, but that isn't true. I have to judge. By mere virtue of the fact that I'm taking 500 pictures and delivering 50, it is inherently the case that I have to use my judgement to decide which of your smiles and facial expressions you'll be most satisfied with and which ones you won't. Sometimes my judgement tells me that you will not be satisfied with any of the pictures I've taken of you, so what do I do? Where is the line between projecting my standards onto someone else and thoughtfully applying the &lt;a href="https://en.wikipedia.org/wiki/Golden_Rule"&gt;golden rule&lt;/a&gt;? My trustworthiness and privilege are on the line here.&lt;/p&gt;
&lt;p&gt;One easy answer is that you should always get everybody's consent before taking their picture. We've been over street photography so I'll skip that kind of consent here. But even in an event photography setting, getting consent is not so clear. Either you're pre-surveying every attendee and memorizing their answers which I would say is quite infeasible, or you're asking them on the spot. And if you ask them on the spot then their answer is contaminated by peer pressure from those around them.&lt;/p&gt;
&lt;p&gt;You can say I am way totally overthinking it, and that my implication that a person could possibly be ugly is itself the most inappropriate and offensive part of my thinking, but I don't have any way to exist that does not involve overthinking. Either I publish the photo or I don't, and I have to have a reason either way. It's either that, or you imply that the final selection of photos is made without reason. This is not an imaginary problem — I have specifically heard people say they don't like having their picture taken or don't like seeing themselves in pictures. And since we don't have all of our insecurities written on our sleeves, it must be the case that more people feel this way than will openly say it, and it has to be my judgement call.&lt;/p&gt;
&lt;p&gt;Another easy answer is to say let's just stop taking pictures entirely, to avoid offending those who don't like it. But on the other hand, plenty of people absolutely love being in pictures, and we know definitively that people want to have these memories, so we're not going to let this be the death of photography, either. Can't be walking on eggshells all the time.&lt;/p&gt;
&lt;p&gt;To some extent I have to operate on the assumption that since you wake up and see yourself in the mirror every day, you're already used to how you look even if you don't, per se, love it. It's not like the photo is going to be a surprise to you. It is altogether worse to be the subject of erasure — as if you were never there at all — than to be shown as less than perfect.&lt;/p&gt;
&lt;p&gt;This is why I'm taken aback when I see street photographers publishing what I would say are unflattering pictures of strangers. I've been describing event photography where there is some level of obligation to include and represent everybody. But when photographing complete strangers on the street, I don't see a lot of artistic merit in getting people in unflattering positions, especially if the composition, framing, and background management are otherwise completely uninspired.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.youtube.com/watch?v=7LwnTdrX9Vg&amp;amp;t=2m36s"&gt;&lt;img alt="" src="/writing/hobby_photography_2/unflattering.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I think one of the reasons candid photography appeals to me, and perhaps other creative types, is that I've always been a behind-the-scenes kind of person. There's a reason movies come with special features, directors commentaries, and behind-the-scenes footage: people want to see what goes into the preparation and production of the main feature; they want to see the things that only the exclusive set of cast and crew got to see. Just as a boom operator does his best to move invisibly across a set, unseen and unknown but crucial to the production of a great work, I try to move invisibly through a crowd and produce great work of my own.&lt;/p&gt;
&lt;p&gt;But there is a double edge here. Being behind the scenes and invisible is both a good and a bad thing. When I place myself in the center of a crowd of dancing people to take their picture, you know what I'm not doing? Dancing with them. I am certainly no good at dancing anyway — guilty feet have got no rhythm — but I still want to engage and socialize when I go out, and I'm finding that this doesn't work very well after I've established myself to the crowd as the candid photographer. Once you've trained them to let you get close without drawing their attention, it's hard to get their attention. Oops. And when I'm photographing my cousin's baby shower or my employer's Christmas party, I'm undoubtedly doing less socializing than most of the other attendees. I'm fine with that because I have more fun taking awesome photos than I do socializing, most of the time. But everybody knows that the photographer usually has the fewest appearances in the final album.&lt;/p&gt;
&lt;p&gt;The camera has demonstrably brought me quite a lot of positive interactions with people I would otherwise not have met, and by sharing my photos with everyone my rolodex has quickly grown. But I have come to recognize that being the privileged eye often comes with being the outsider eye, and if I want to go out with the express purpose of meeting people or making friends, it probably has to be sans camera.&lt;/p&gt;
&lt;p&gt;And don't tell me I'm artistic.&lt;/p&gt;
&lt;h2 id="videography"&gt;Videography&lt;a class="header_anchor_link" href="#videography"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Photography and videography seem pretty similar to most people outside of the hobby, but wow they are completely different skillsets. Some bands have asked me to shoot video for them, and of course I can't say no. Video is a lot harder because every little bump, wobble, or tilt of the camera is recorded permanently, and you can't just delete the bad milliseconds like you can with stills, so the stakes are a lot higher. The temporal element of video means you have to do the right things at the right time in the right order, whereas with stills you can play fast and loose with the presentation order if you so choose. Video is also more difficult for a few technical reasons. For example, your shutter speed can't be too fast or too slow, especially if you want to stick to the traditional &lt;a href="https://en.wikipedia.org/wiki/Rotary_disc_shutter"&gt;180 degree shutter&lt;/a&gt;, so you lose access to one third of your exposure triangle.&lt;/p&gt;
&lt;p&gt;Shooting photo and video as a hybrid is kind of stressful for me because whenever I'm in video I feel like I'm missing out on good photo moments, and whenever I'm in photo I feel like I'm missing out on good video moments. And if I start a video even a few seconds too late and miss the beginning of a song, I feel like the whole thing is soiled, so I'm constantly on edge trying to figure out when to switch. And then the whole time I'm not sure if I'm getting the best angle or focusing on the best person.&lt;/p&gt;
&lt;p&gt;One of my biggest sources of music videography inspiration is Naver Onstage which took (RIP) a very mobile approach to camerawork. The camera operator basically &lt;a href="https://www.youtube.com/watch?v=SmTRaSg2fTQ"&gt;never&lt;/a&gt; stops &lt;a href="https://youtube.com/watch?v=3SBb0LbvrrY"&gt;moving&lt;/a&gt;, breaking into and out of the artists' personal space and sometimes eliciting their hammy reactions. Unfortunately this is REALLY HARD to do well when you're hand-holding the camera without any stabilization equipment. And it's also really hard to point the camera at the right person at the right time when you don't know how they're going to move and don't especially know when they're going to do a solo. I've come to appreciate the value of a wide, static vantage point which lets the viewer focus on whoever they want the whole time.&lt;/p&gt;
&lt;p&gt;&lt;video controls="" poster="https://files.voussoir.net/photography/2024-03-17 HotHead St Patricks Day/small_2024-03-17_17-33-35 Creep.jpg" preload="none" src="https://files.voussoir.net/photography/2024-03-17 HotHead St Patricks Day/2024-03-17_17-33-35 Creep.mkv"&gt;&lt;/video&gt;&lt;/p&gt;&lt;p&gt;&lt;video controls="" poster="https://files.voussoir.net/photography/2024-06-15 Van Sutton/small_2024-06-15_21-28-41 Drum Solo.jpg" preload="none" src="https://files.voussoir.net/photography/2024-06-15%20Van%20Sutton/2024-06-15_21-28-41%20Drum%20Solo.mov"&gt;&lt;/video&gt;&lt;/p&gt;&lt;p&gt;&lt;video controls="" poster="https://files.voussoir.net/photography/2024-08-16 Craic Haus/small_2024-08-16_19-23-00 Drunken Sailor.jpg" preload="none" src="https://files.voussoir.net/photography/2024-08-16%20Craic%20Haus/2024-08-16_19-23-00%20Drunken%20Sailor.mkv"&gt;&lt;/video&gt;&lt;/p&gt;&lt;p&gt;I am still learning what works and what doesn't. I try not to rule something out until I give it a shot. One thing that I have tried several times and almost always regretted is turning the camera away from the band to get the audience reaction. In my stills, the audience photos are often some of the best, because I think it's important to show the effect the band has on their crowd — that's the whole point of live performance. But I don't like the way it translates to video for a single-camera shot. In the moment, it always seems like an important thing to do, but when I watch the video back again I always feel like I missed out on seeing whatever the band did during those seconds, or else the whip-pan across the audience is so hurried as to not miss out that it winds up nauseating instead. I think audience reactions are better done with multiple camera operators. The exception to this is when you can get an angle from beside or behind the band so you get both at once.&lt;/p&gt;
&lt;p&gt;Oh and the gigabytes. So many gigabytes.&lt;/p&gt;
&lt;h2 id="money_and_recognition"&gt;Money and recognition&lt;a class="header_anchor_link" href="#money_and_recognition"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One of the biggest reasons I do photography at all is because I want to make connections with people. Most of the photos I take are given away for free because it makes people feel good and doesn't cost me anything except some time.&lt;/p&gt;
&lt;p&gt;The Friday night concerts have become my &lt;a href="https://www.youtube.com/watch?v=VvdQ381K5xg" title="Not Just Bikes - The Great Places Erased by Suburbia (the Third Place)"&gt;third place&lt;/a&gt; for what feels like the first time in my life. I know the names of maybe three dozen regulars now.&lt;/p&gt;
&lt;p&gt;&lt;audio controls="" src="/writing/hobby_photography_2/nationalgeographic.flac"&gt;&lt;/audio&gt;&lt;/p&gt;&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/cards.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;In July, I finally got cards made. Throughout the month of June I got more "do you have a card?"s than ever before. And I hesitated for a long time because I still don't &lt;em&gt;really&lt;/em&gt; want photography to be a business or a hustle for me, and I don't want to give the impression that it is by handing out cards. But I finally went ahead for two main reasons:&lt;/p&gt;
&lt;p&gt;First, because people are literally asking me for it. They want to see my photos and I want to show them. If I were handing out cards to strangers unprompted, it would be spam and solicitation, which I want to avoid. But if they're the ones asking me for it, and I tell them "I don't have any cards because I don't want to give the impression that I'm soliciting for business", that would really make the interaction more weird than just "yes, here you go". It really is so much faster to hand someone a card than to exchange phone numbers verbally or have them scan a QR code off of my phone screen, which they will lose in their browser history and ask for again next week. Ask me how I know.&lt;/p&gt;
&lt;p&gt;Second, even though photography is not my hustle and I don't solicit for business, I'm not going to turn down cool opportunities that present themselves to me. If someone asks to hire me and it sounds fun, yeah I'll take them up on it. I keep telling myself this isn't what I came here to do, but if I stop pushing my boundaries, I'll stagnate. More outings, more new experiences, more connections with more new people. &lt;a href="https://en.wikipedia.org/wiki/Yes_Man_(film)"&gt;I'm not just gonna say no&lt;/a&gt;. One week before this publication date, I've just shot my first wedding — something I said from the beginning I would not do — and I think I did just fine.&lt;/p&gt;
&lt;p&gt;One boundary I'd like to maintain is I don't want to take a job that prevents me from self-publishing the pictures to my website, unless it's something like a private party. For anything in the more public, performative, or documentative sphere, exclusivity and blackholing my work into somebody else's pockets are not in line with my ethos.&lt;/p&gt;
&lt;p&gt;The fact of the matter is the money I get from paid shoots is not a big deal to me. My day job pays me well enough and I am not relying on photography for income the way categorical professionals do. I also don't want to charge too much because I'm not really confident I'll do a good job, especially in a difficult shooting environment like a dimly lit bar, or for a type of work I've never done before. I can barely trust myself to shoot street on a sunny day because dynamic range is hard. And finally, everybody knows that musicians are starving artists, probably not making tons of money from a gig in the first place. I have heard first-hand from bands that they're not getting paid by tonight's venue, or maybe it's only $100 a head. Without sounding too magnanimous, I don't feel like I need to take too much from them.&lt;/p&gt;
&lt;p&gt;Does it hurt professional photographers when people like me are out there charging hobbyist prices? I can imagine the answer is yes. But part of the question is whether these bands would have sought a professional photographer in my absence. The reality I'm seeing so far is that a lot of local bands don't have many good pictures of themselves because they've never hired a photographer before. And I'm not soliciting them for business, they're the ones asking to hire me. What this tells me is they had a latent hunger for photos, and are willing to pay for them, but never reached out to hire a professional until I (an amateur) fell into their lap.&lt;/p&gt;
&lt;p&gt;I am normally quite critical of any rhetoric that speaks of preserving jobs. The example I've used before is the &lt;a href="https://en.wikipedia.org/wiki/Switchboard_operator"&gt;telephone switchboard operator&lt;/a&gt; — sometimes jobs become obsolete, and although I'm sure the switchboard operators were unhappy to lose their job, life goes on. So if amateur photographers can run around doing shoots for free or cheap and professionals go out of business, that might be seen as the free market at work. The rebuttal is that professionals will deliver pictures of significantly higher quality in terms of lighting, posing, composition and blocking, and tasteful editing, and that this is all worth paying for. The counter-rebuttal is that the majority of the population are normies without taste, and they will be satisfied by amateur pictures just as they are satisfied by sloppy fast food, and they the market will not bear the cost of higher quality work. Sometimes I give people pictures that I think are lousy because that's all I had available, and they tell me they love them. As an agent in this market, I can accelerate this effect by doing a mediocre job for a mediocre price. But since I do have taste and can appreciate that professionals make better work than me, I'll stay out of their way and not massively undercut them all the time. This is how I defend that I'm not hypocritically giving a job-preservationist argument, but rather this is an argument for preservation of quality in craft. Maybe the same could have been said for the switchboards, but it would have been somebody else's position to take.&lt;/p&gt;
&lt;p&gt;When I attend and shoot an event on my own accord, I'll give away all the photos for free, but when someone's specifically asking me to do it, it seems essential that I charge at least a bit. As much as I like giving things away, it has to be clear that it's my decision. I don't want people to think they can call me out for free work whenever they want.&lt;/p&gt;
&lt;p&gt;Well, luckily, that hasn't been a problem so far. More commonly, people are handing me money that I never asked for. One of the bands I shot gave me the contents of their tip jar, unprompted, and another caught up with me on a later Friday to give me a &lt;a href="https://old.reddit.com/r/thathappened"&gt;hundred bucks&lt;/a&gt;. More than once, I've taken a candid picture of a dancer or audience member, and given it to them, and they've insisted on tipping me. My instinct is to decline — I want to tell them that art is better when it's free. But I also feel that if I decline the tip, I am denying them from expressing their side of the interaction, forcing things to always go my way. In a way, it is more selfish to deny the tip than to accept it, because it comes off as virtue signaling or magnanimous or like I'm the only one who's allowed to be generous.&lt;/p&gt;
&lt;p&gt;The most surprising variation of this is when a complete stranger emerges from a crowd to start this exchange:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Them: Could you take a picture of us?&lt;/p&gt;
&lt;p&gt;Me: Sure&lt;/p&gt;
&lt;p&gt;Them: How much?&lt;/p&gt;
&lt;p&gt;Me: ...Nothing?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;audio controls="" src="/writing/hobby_photography_2/2024-05-24_19-18-30.flac"&gt;&lt;/audio&gt;&lt;/p&gt;&lt;p&gt;&lt;audio controls="" src="/writing/hobby_photography_2/2024-07-28_16-00-00.flac"&gt;&lt;/audio&gt;&lt;/p&gt;&lt;p&gt;These ones have been really surprising to me. The usual narrative from people online is that no one, not even, or perhaps especially not the ones who can afford it most, want to pay for the commodity that is photography when everybody has a camera in their pocket. So I never would have expected that random people would suggest paying me for a photo on the spot. It's not worth me taking your money for that, you can have it for free, guys.&lt;/p&gt;
&lt;p&gt;Now, maybe there's actually nothing to this. Maybe if I had answered anything other than free they would have rescinded their request. Even for a dollar, five dollars?&lt;/p&gt;
&lt;h2 id="zooming_out"&gt;Zooming out&lt;a class="header_anchor_link" href="#zooming_out"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Telephoto makes everything look good. Too good, maybe. I'm trying to get better at embracing wider focal lengths so I can capture more of the subject's context and environment rather than a dramatic eye-candy closeup.&lt;/p&gt;
&lt;p&gt;One of the reasons I struggle with zooming out more is my perception of the image size on the viewfinder or rear screen. Because these screens are physically small, I zoom into the subject more than I should so that they are nice and prominent on the little viewfinder. Then I get home and look at the image on my 27" monitor and WOAH that's too big. These pictures usually get deleted and I miss some good ones this way.&lt;/p&gt;
&lt;p&gt;It's like I'm afraid that if I don't zoom in enough, the subject will be too small in the image, even though I already know from experience that 26mp is enough to capture individual eyelashes from a surprising distance and there is absolutely no reason to worry about this. I need to trust my equipment and my ability to judge focus without overfilling the frame.&lt;/p&gt;
&lt;p&gt;Here's an example of my problem. This is a cool guy doing a cool thing; I'm happy with the timing of the shot and I like how the sunlight shows off his muscles but silhouettes his face. But the crop is too tight — without seeing his feet and the ground below, you can't get an appreciation for how high he is.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/2024-04-28_16-46-44.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;I came back a month later to try again. That's better.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/2024-05-26_17-58-24.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;The other reason I struggle with wide angles, especially in black and white, is that I feel like the picture just turns to noise. One reason I go for a wide angle is to capture a large crowd. I want the viewer to see how many people were at this event. So far I have not done a good job and I think these photos are pretty terrible to look at. But I keep a few of them because that's all I've got so far.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2024-06-14_18-58-21.jpg" /&gt;&lt;/p&gt;
&lt;h2 id="website_portfolio_gallery"&gt;Website, portfolio, gallery&lt;a class="header_anchor_link" href="#website_portfolio_gallery"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Currently, &lt;a href="https://voussoir.net/photography"&gt;my website&lt;/a&gt; is a simple chronology of albums and a few single photos, newest first. But sooner or later I'll have to come up with a redesign. Already there are so many pictures on the page that it can be problematic scrolling through.&lt;/p&gt;
&lt;p&gt;Because I might have redesigned the page by the time you're reading this, &lt;a href="/writing/hobby_photography_2/voussoir_net_photography.html"&gt;here is an abbreviated snapshot of the homepage&lt;/a&gt;. Each album shows a couple large highlights, and then a bunch of tiny thumbnails. The highlights are direct links to the jpg file, but the tinies are linked to the album with an anchor to that photo. I came up with this design because I wanted every photo in the album to be immediately visible without clicking into the album, lest they be never seen at all. Maybe the highlights I chose don't speak to you, but something in the tinies catches your attention. And also because I want to give everybody representation even if their picture didn't make the highlights. Once you click into the album, every picture is big.&lt;/p&gt;
&lt;p&gt;Most of the people I meet at the Friday concerts are, well, older than me. As the kind of person that writes my HTML by hand and encourages scraping/downloading, I am deliberate in using simple anchors and img tags instead of obscuring the tags behind lightboxes and other javascript dynamism. But now I have seen firsthand the way my page confuses people. They tap on a highlight to see it bigger, and it goes to the direct image file (16 megabytes, sorry data plan!) and they have to use their browser back button to get out. And when they tap on a tiny, it doesn't bring them to a direct image, but still they have to use their back button to get out of the album and back to the homepage so they can see the next album. And although I'm trying to show off a couple highlights and move on to the next album, they want to see &lt;em&gt;every single picture&lt;/em&gt; so they pinch-zoom on the tinies which is not a good UX. Oh yeah, did you notice I'm only using phone verbs here (tap, pinch) because this is all happening on their phone and of course these pictures, especially being mostly horizontal, look a lot better on a computer monitor in the first place.&lt;/p&gt;
&lt;p&gt;The design of an archive like this is challenging. If it were just a list of links with no pictures, there'd be nothing for the viewer to judge what they might be interested in clicking on. But if it's a complete dump of every single picture on one page (like I'm doing now), then it becomes difficult to scroll through. And if it's just one or a few highlights with a "click to see the rest" link, then I feel guilty for anyone who didn't make the highlights because it says they weren't important enough.&lt;/p&gt;
&lt;p&gt;I have already made my case for keeping some bad photos as part of an album, but that doesn't mean they need to be front and center as the first thing a new visitor sees. I don't feel the need to play that off as some kind of rawness or authenticity. I think I should come up with a way to show off my bests or favorites, but I don't want those to become stale while new stuff is coming in, either. I am also thinking that there will come a day when I shoot a more significant project that deserves to stay at the top of the page for a while instead of immediately falling off the chronology under the next band shoot. No offense, bands.&lt;/p&gt;
&lt;p&gt;If I redesign the front page to only show a smaller selection of photos, I will always provide some kind of index/archive page with a complete list of everything. If, for some reason, you want to scrape my site and download every photo I've published, I want it to be easy for you to do that. Because that's exactly what I do when I find something I like, too. This is in stark contrast to most other photographers' websites which deliberately obfuscate their archives, or make it difficult to download the photos, or only provide low-res copies. What's even the point of capturing history if you're not going to share it.&lt;/p&gt;
&lt;p&gt;My portfolio site is &lt;a href="https://git.voussoir.net/voussoir/voussoir.net/src/branch/master/voussoir.net/photography/generate_site.py"&gt;statically generated&lt;/a&gt; from my photo database. I'm happy with that, but it's always been a barrier to adding any bespoke elements or sorting things in a non-chronological way. Now that I'm thinking through this out loud, what I'll probably do is use the generator to make the archive page, and handwrite the front page as its own HTML file. Then I'll have the freedom to adjust the front page as often as I want without disturbing the steadiness and reliability of the archive.&lt;/p&gt;
&lt;p&gt;So far, I have taken pride in the fact that my photo gallery contains no text on the page except the album titles. No descriptions, no context, no genre tags, no apologies for the photos that aren't great because that would be way too many apologies. I just let the photos speak for themselves, the only editorialization being my selection of a few highlights from each album to be full-size over the rest of the thumbnails. But when I want to write something about it, I put it in /writing. But I think I will have to add genre categories in some way, because if someone (including my future self) just wants to see one genre it can be a nuisance to scroll past all the other stuff. The deliberate lack of categorization is an artistic stance that says "you can't label me" which is cool in a punk kind of way for a few minutes, but it stops being cool when it hinders the usability of the page. I mean, hindering usability for the sake of a message is exactly what an artist would do.&lt;/p&gt;
&lt;p&gt;When people publish photobooks, they usually cover several months or even years with only a few dozen pictures. Meanwhile, on my site I publish a few dozen pictures from every single outing. I often get the feeling that I'm publishing way too much, and it makes it harder to find the good ones amongst the bad and middling ones. But my answer to this feeling is the same answer I've given so many times already — I want to publish albums that the subjects themselves would be glad to have, and I make the effort to share it with them directly. I don't want to cull everything down to the 0.1% greatest hits for the benefit of an imaginary, remote, anonymous internet audience.&lt;/p&gt;
&lt;h2 id="instxgrxm"&gt;Instxgrxm&lt;a class="header_anchor_link" href="#instxgrxm"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the past year I've learned something that I really did not expect. It seems that for people under the age of about 40, instagram is by far the number one way they want to exchange contacts. Some older people have asked if I'm on Facebook, but besides that it has all been "what's your instagram?". Nobody has ever asked to connect on any other platform, and I mean &lt;strong&gt;never ever&lt;/strong&gt;. It's really crazy how dominant it is. My answer is of course that I don't use instagram and we'll have to trade phone numbers instead. Or, nowadays, here's my card.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/2024-07-10_22-32-32.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/2024-07-10_22-32-17.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/2024-07-10_22-36-10.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/2024-08-01_00-17-00.png" /&gt;&lt;/p&gt;
&lt;p&gt;One time I asked a girl out to lunch, and she said no, but she offered that we could add each other on instagram. That had nothing to do with photography — that was just instagram being used as a phone number substitute.&lt;/p&gt;
&lt;p&gt;On one hand I understand the desire for a phone number substitute because giving out your phone number feels like a sensitive thing, but on the other hand if you exchange instagram with everyone you meet then doesn't instagram become the most sensitive thing?&lt;/p&gt;
&lt;p&gt;I already knew that people still use instagram in 2024, but I had assumed that between the aggressive prioritization of video reels, the rise of influencer culture, and the engagement-maximizing algorithmic changes, that instagram was just a place of consumption / doomscrolling at this point. I really had no idea that for so many people this was their primary way to communicate.&lt;/p&gt;
&lt;h2 id="how"&gt;how&lt;a class="header_anchor_link" href="#how"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I started drafting this article in May so that I would have plenty of time to think and write before publishing in October. Immediately, I wrote "I leave my camera in manual focus because I've given up on the X-T3's autofocus". But I realized it would be a much better use of these five months to challenge myself and learn how to use the autofocus better, instead of leaving that willfully ignorant stance just sitting on this page the whole time.&lt;/p&gt;
&lt;p&gt;... so anyway, I leave my camera in manual focus because I've given up on the X-T3's autofocus.&lt;/p&gt;
&lt;p&gt;My first assumption was that I don't need AF-S because I use M with a function button for focusing. I thought that M+FBF was equal to or better than AF-S because I can focus when I want to and let go of the shutter button without refocusing. But there were two things I hadn't considered: AF-S with face detect will also expose for the face which I'd have to do myself in M; and AF-S with focus priority will wait until the picture is in focus before firing, whereas in M I'd try my best and maybe get it wrong and not know it was blurry until later. So I gave AF-S a try for a while to see if I had just been narrowmindedly been stuck in M this whole time... and now I'm back already. Fuji's AF still misses a lot and it goes hunting for faces in the background. Believe me, my success rate with M+FBF is also less than 100%, but at least then I know it's my fault and not the camera's.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/captcha.png" /&gt;&lt;/p&gt;
&lt;p&gt;My second assumption was that I don't need AF-C because when the subject is moving quickly I won't be able to trust the camera to keep up, and if they're moving slowly then I'll be fine with M+FBF. And, yeah, I wasn't able to crack this one either. All I know is that even when I'm shooting what I'd consider an easy subject, the face/eye detect will jump all around, off into deep space, and it'll spend maybe 20-40% of its time not in correct focus. Then you throw sunglasses or hats into the mix and it gets harder, and then the person turns around so the camera finds something else to focus on, and by the time they face forward again now you're starting from scratch.&lt;/p&gt;
&lt;p&gt;Keep in mind the X-T3 is now two generations behind, so I don't have experience with Fuji's AF of today. But everyone knows that Fuji is behind competitors in terms of AF, so if I was using one of the other leaders I might have the totally opposite stance here and use AF-C for everything.&lt;/p&gt;
&lt;p&gt;I know very well that trying a tool for a few minutes and quickly giving up is not a good-faith evaluation of the tool. I know that there is a learning period, and you have to &lt;a href="https://www.youtube.com/watch?v=Q6mU7A0a3Mg" title="The Brood opening scene"&gt;go all the way through it to the end&lt;/a&gt;. The problem is I don't feel like I have opportunities for low-stakes practice on real people long enough to get through it. What I mean is that if I try to shoot an entire Friday Night concert in AF-S or AF-C without falling back to M, I'm going to ruin or miss out on a bunch of shots and I'm going to disappoint that band by giving them my worst album of the season. Or if I try to do this at the beach I'll ruin all the shots there and feel like I wasted the day. And I don't have anyone that I feel comfortable asking to model for me for practice.&lt;/p&gt;
&lt;p&gt;Excuses, excuses. I don't pretend any of this is justified. I am lazy, stubborn, and undisciplined and trying to rationalize it.&lt;/p&gt;
&lt;center&gt;* * *&lt;/center&gt;&lt;p&gt;I listened to an audio recording I made last year and was genuinely surprised to hear the autofocus beep. It's been so long since I've heard it, I forgot it ever existed. I turned it off one night while shooting an orchestra concert and off it stayed.&lt;/p&gt;
&lt;p&gt;I am still not very interested in editing. I only do basic levels adjustments to fix over/under exposure, and occasionally I'll crop to fix a bad composition or remove a distracting background figure. I don't like to crop, because it represents a failure to compose right the first time, and because it throws away hard-earned megapixels, but sometimes you just have to.&lt;/p&gt;
&lt;p&gt;If I'm shooting something very important then I'll shoot raw+jpeg, because yes the raws will absolutely rescue an exposure that is unfixable in jpeg. I just prefer to not need rescuing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/bouquet.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;Underexposed is better than blurry. When the sun is setting, crank the ISO and open the aperture and hold on to your shutter speed for as long as you can. A dark photo can be brightened in post, but a blurry photo is ruined forever. This works better in black and white than in color.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2024-07-28_19-58-27_before.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="/writing/hobby_photography_2/thumbs/2024-07-28_19-58-27_after.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;By the way guys raw is not an acronym.&lt;/p&gt;
&lt;h2 id="fuji_dial_slipups"&gt;Fuji dial slip-ups&lt;a class="header_anchor_link" href="#fuji_dial_slipups"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At some point, my camera will be old and battered enough to warrant replacing, and naturally I'll look for a new Fuji. But some of Fuji's recent changes have me worried that the X-T3 might wind up being the peak of their design, and they might be on a downtrend for a while.&lt;/p&gt;
&lt;p&gt;In the transition from X-T3 to X-T4 they replaced the photometry/metering dial with the stills-movie switch. In the X-T3, movie mode is part of the drive dial.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fujifilm-dsc.com/en/manual/x-t3/about_this_camera/parts/index.html"&gt;&lt;img alt="" src="/writing/hobby_photography_2/xt3_photometry.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fujifilm-dsc.com/en/manual/x-t4/about_this_camera/parts/index.html"&gt;&lt;img alt="" src="/writing/hobby_photography_2/xt4_moviemode.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I can't help but feel that this is an obviously poor decision and that movie mode belongs on the drive dial. The drive dial is the master switch that sets the context for every other feature on the camera. The drive modes are mutually exclusive with one another, not only because the switch cannot physically be in two positions at once, but because it is logical that you can take photos or videos but not both at once. By pulling movie mode out into its own dial, now there is a matrix of Drive/Movie dial positions that are all meaningless. What is Movie+CL? Nothing. What is Movie+Bracket? Nothing. What is Movie+Panorama? Nothing. If the movie mode is going to continue being mutually exclusive with all other modes, it should have stayed on the drive dial.&lt;/p&gt;
&lt;p&gt;Then, in the X-T5 they kept it this way despite the marketing fluff about "coming back to our roots" and being "photography first", explicitly acknowledging that &lt;a href="https://www.youtube.com/watch?v=0MTCbj_VDa0&amp;amp;t=6m20s" title="X Summit Tokyo 2022"&gt;the X-H series are the hybrids, not the X-T series&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fujifilm-x.com/en-us/products/cameras/x-t5/"&gt;&lt;img alt="" src="/writing/hobby_photography_2/xt5_photography_first.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fujifilm-x.com/en-us/series/fujifilm-x-t5/ready-to-party/"&gt;&lt;img alt="" src="/writing/hobby_photography_2/xt5_roots.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I have no problem saying that the photometry dial is the least used dial on my X-T3. But there is no point throwing away a seldom-used feature to make way for a switch that is mutually exclusive with all other choices on the drive dial, and especially not in the product line that claims to be photo-first.&lt;/p&gt;
&lt;p&gt;And now, in the X-T50, they've replaced the ISO dial with a film simulation dial.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://fujifilm-dsc.com/en-int/manual/x-t50/about_this_camera/parts/"&gt;&lt;img alt="" src="/writing/hobby_photography_2/xt50_filmdial.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The X-T*0 line is meant to be more amateur than the X-T* line, but wow, I really do not think people need to change their film simulation more often than their ISO. I don't know what kind of psychedelic photo albums Fuji is expecting people to shoot, where every photo in the sequence is a completely different color tone. I'm not sure if that's low art or high art. Deprioritizing the exposure triangle like this is not cool, even in the amateur product line, because you're supposed to give the artist room to grow, not pad them in. Besides, most people probably have one or two favorite film sims, and the rest will never get used. Hey instead of a stills-movie switch can I get a Provia-Acros switch?&lt;/p&gt;
&lt;p&gt;So, yeah, I'm not sure what my options will be when it's time for a new camera. To be honest I'd like to get into medium format, i.e. the GFX line, but the dials there are even less impressive. X-T3 peak?&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="header_anchor_link" href="#conclusion"&gt; (§)&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="/writing/hobby_photography"&gt;previous article&lt;/a&gt; I said I might look back on those opinions in three months and find them unrecognizable. Here we are at two years and that's not the case. Hooray! Seems like my core beliefs are pretty stable, and right now it's just a matter of improving my skills and seeking bigger, more important work.&lt;/p&gt;
&lt;p&gt;That's all I've got to say for now.&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commits/master/voussoir.net/writing/hobby_photography_2/hobby_photography_2.md"&gt;View this document's history&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://git.voussoir.net/voussoir/voussoir.net/commit/34430fa3c647efdf5b3d0d655460e395eb59864d"&gt;&lt;time datetime="2024-10-15T21:47:59-07:00"&gt;2024-10-15&lt;/time&gt; Add hobby_photography_2.md.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;address&gt;Contact me: writing@voussoir.net&lt;/address&gt;
&lt;p&gt;If you would like to subscribe for more, add this to your RSS reader: &lt;a href="/writing/writing.atom" rel="alternate" type="application/atom+xml"&gt;https://voussoir.net/writing/writing.atom&lt;/a&gt;&lt;/p&gt;
&lt;/article&gt;</description><author>voussoir.net/writing</author><pubDate>Wed, 16 Oct 2024 07:47:59 GMT</pubDate><guid isPermaLink="true">https://voussoir.net/writing/hobby_photography_2</guid></item><item><title>ThinkPad as a server: third time's the charm</title><link>https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;This is a follow-up to my two previous attempts on this topic:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ounapuu.ee/posts/2022/05/10/thinkpad-as-a-home-server/"&gt;Can a laptop from 2012 be a viable home server?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ounapuu.ee/posts/2023/01/27/thinkpad-as-server-followup/"&gt;ThinkPad as a server: the follow-up&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Since then, I&amp;rsquo;ve had quite a few changes to my home server setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ounapuu.ee/posts/2023/09/07/ikea-powered-homelab/"&gt;I put my home server stuff on an IKEA pegboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ounapuu.ee/posts/2023/10/09/zimaboard/"&gt;I tried the Zimaboard&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ounapuu.ee/posts/2022/01/17/asrock-x300-future-of-desktops/"&gt;I switched back to the ASRock DeskMini&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ounapuu.ee/posts/2024/06/24/back-to-roots/"&gt;I got fiber again!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Zimaboard was
great, &lt;a href="https://ounapuu.ee/posts/2023/10/09/zimaboard/#2024-08-20-update"&gt;until the CPU became just a little bit too slow for my needs.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;ASRock Deskmini X300 was great, until I learned that it&amp;rsquo;s actually relatively bad at idle power usage due to lack of
lower sleep states.&lt;/p&gt;
&lt;p&gt;Combined with my recent tech cleanup, I am now proudly running all of my services on a single home server, powered by
&lt;a href="https://ounapuu.ee/posts/2022/01/09/why-i-went-back-to-using-a-thinkpad-from-2012/"&gt;my trusty ThinkPad T430.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The promise of USB-C &lt;em&gt;everything&lt;/em&gt; was too good to pass up, so I stopped resisting.&lt;/p&gt;
&lt;p&gt;Doesn&amp;rsquo;t mean that I won&amp;rsquo;t run hardware into the ground.&lt;/p&gt;
&lt;p&gt;Compared to last two attempts, I&amp;rsquo;ve made a couple of important changes to the setup.&lt;/p&gt;
&lt;p&gt;There is a weekly &lt;code&gt;tlp recalibrate BAT0&lt;/code&gt; job scheduled that completely drains the battery and charges it up again, which
should make sure that the laptop battery survives for a longer time.
The battery charge threshold is set to 80%.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve used a knock-off Honeywell PTM7950 thermal pad on the CPU. The cooling performance is comparable to using thermal
paste, but hopefully it doesn&amp;rsquo;t drip out.&lt;/p&gt;
&lt;p&gt;The whole setup is mounted to an IKEA pegboard using
a &lt;a href="https://nillkin.org/accessories/nillkin-prodesk-adjustable-laptop-stand"&gt;Nillkin ProDesk laptop stand&lt;/a&gt;
and a generous amount of zip ties. The combination of zip ties and the rubber feet on the stand result in the stand
barely moving once mounted. The laptop stand itself is very sturdy, and you&amp;rsquo;ll have to use a lot of force to change its
angle.&lt;/p&gt;
&lt;p&gt;The CPU uses less power compared to the previous one. The one in the server is
the &lt;a href="https://ark.intel.com/content/www/us/en/ark/products/71670/intel-core-i7-3632qm-processor-6m-cache-up-to-3-20-ghz-bga.html"&gt;Intel i7-3632QM&lt;/a&gt;,
a quad-core CPU with a 35W TDP.&lt;/p&gt;
&lt;p&gt;I still have some scripts running to limit the CPU temperatures to 85°C, just to be on the safe side and avoid
overheating at all costs.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/media/setup-1.jpg"&gt;
        &lt;img alt="Took me 20 minutes to migrate the server along with the drives, and 2 hours to get the zip tie mounting just right." height="750" src="https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/media/setup-1_hu_bd1a052b3026c677.jpg" style="width: auto; height: auto; border-radius: 8px;" width="1000" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Took me 20 minutes to migrate the server along with the drives, and 2 hours to get the zip tie mounting just right.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/media/setup-2.jpg"&gt;
        &lt;img alt="The small shelf attachment holds two external drives and the power adapter." height="750" src="https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/media/setup-2_hu_8e9259980d09bafb.jpg" style="width: auto; height: auto; border-radius: 8px;" width="1000" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      The small shelf attachment holds two external drives and the power adapter.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/media/setup-3.jpg"&gt;
        &lt;img alt="Another angle." height="1000" src="https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/media/setup-3_hu_6fd191d386f8416d.jpg" style="width: auto; height: auto; border-radius: 8px;" width="750" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Another angle.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/media/setup-4.jpg"&gt;
        &lt;img alt="Plenty of clearance for adequate cooling." height="1000" src="https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/media/setup-4_hu_a188950a4ad58688.jpg" style="width: auto; height: auto; border-radius: 8px;" width="750" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Plenty of clearance for adequate cooling.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;The power consumption is improved on this setup:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mean: 27 W&lt;/li&gt;
&lt;li&gt;min: 23 W&lt;/li&gt;
&lt;li&gt;max: 70 W&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Results on the DeskMini on a typical day:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;mean: 34.8 W&lt;/li&gt;
&lt;li&gt;min: 28 W&lt;/li&gt;
&lt;li&gt;max: 89 W&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All measurements include the power consumption of the UPS, fiber PON and the router, which is around 10 W.&lt;/p&gt;
&lt;p&gt;And the performance is good enough.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m happy with it.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s also handy that I now have that one laptop that I can sticker bomb without feeling guilty about removing them later
on.&lt;/p&gt;
&lt;h3 id="2025-june-update"&gt;2025 June update&lt;/h3&gt;
&lt;p&gt;I have been running the ThinkPad T430 as a home server for a long time now. In fact, it&amp;rsquo;s one of the longest stints I&amp;rsquo;ve
had with a specific home server setup. The battery recalibration was &lt;a href="https://ounapuu.ee/posts/2025/05/15/home/"&gt;temporarily disabled&lt;/a&gt;
because of it interfering with UPower configuration that I had in place for shutting down the laptop at a higher battery
percentage (40%), but in all other aspects it&amp;rsquo;s doing fine, and after removing UPower, the battery recalibration works
again.&lt;/p&gt;
&lt;p&gt;There was &lt;a href="https://ounapuu.ee/posts/2025/06/06/thinkcentre-m900-tiny/"&gt;a short week-long experiment with a ThinkCentre M900 Tiny,&lt;/a&gt; but
that got put on pause because the USB-connected drives were a bit flaky. The ThinkPad T430 has proven itself to be a
more stable server platform as a result.&lt;/p&gt;</description><author>./techtipsy</author><pubDate>Wed, 16 Oct 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/10/16/third-times-the-charm/</guid></item><item><title>Accessing direnv environment variables in a Jupyter notebook</title><link>https://www.danielcorin.com/til/juypter/envrc-variables/</link><description>Accessing direnv environment variables in a Jupyter notebook</description><author>Thought Eddies</author><pubDate>Wed, 16 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/juypter/envrc-variables/</guid></item><item><title>Journal Practice, So Far</title><link>https://fractaldragon.net/posts/2024-10-16-journal-practice-so-far.html</link><description>&amp;lt;!doctype html&gt;

    
        
        
        
        Journal Practice, So Far - Recursive Ramblings
        
    
    
        &lt;header&gt;
            &lt;div class="logo"&gt;
                &lt;a href="../"&gt;Recursive Ramblings&lt;/a&gt;
            &lt;/div&gt;
            &lt;nav&gt;
                &lt;a href="../about.html"&gt;About&lt;/a&gt;
                &lt;a href="../contact.html"&gt;Contact&lt;/a&gt;
                &lt;a href="../archive.html"&gt;Archive&lt;/a&gt;
		&lt;!-- &lt;a href="/tags/"&gt;Tags&lt;/a&gt; --&gt;
		&lt;a href="../now"&gt;Now&lt;/a&gt;
		&lt;a href="../TIL/"&gt;TIL&lt;/a&gt;
		&lt;!--
		    &lt;a href="/atom.xml"&gt;Atom&lt;/a&gt;
                    &lt;a href="/rss.xml"&gt;RSS&lt;/a&gt;
		    --&gt;
            &lt;/nav&gt;
        &lt;/header&gt;

        
          &lt;h1&gt;Journal Practice, So Far&lt;/h1&gt;
	  

            &lt;article&gt;
    &lt;section class="header"&gt;
      Posted on 2024-10-16
      
      
      &lt;div class="row tag-block"&gt;
        
        &lt;span&gt; &lt;a href="../tags/lifestyle.html"&gt;[lifestyle]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/100DaysToOffload.html"&gt;[100DaysToOffload]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/journal.html"&gt;[journal]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/Joplin.html"&gt;[Joplin]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/notes.html"&gt;[notes]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/habits.html"&gt;[habits]&lt;/a&gt;&lt;/span&gt;
        
      &lt;/div&gt;
      
      
      &lt;br /&gt;
      Tim Lavoie
      
    &lt;/section&gt;
    &lt;section&gt;
        &lt;h2 id="easy-once-the-habit-is-formed."&gt;Easy, once the habit is formed.&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;tl;dr: If it interests you, just start doing it.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It has been over a year now since I started writing some form of
daily-ish journal. The point isn’t so that I can provide any formal
reckoning for my time, or hope for a bestseller autobiography, but
rather it just helps me to look back on something I did, or thought
about, that I might like to re-examine.&lt;/p&gt;
&lt;p&gt;The first one was in August, 2023. There isn’t necessarily a ton of
structure to these, just that they are in a Joplin notebook called
“Journal”, and I name them according to the &lt;a href="https://www.iso.org/iso-8601-date-and-time-format.html"&gt;ISO
8601&lt;/a&gt; date
format, e.g. “YYYY-MM-DD”.&lt;/p&gt;
&lt;p&gt;The content is largely just a stream-of-conciousness in Markdown
format. I do add tags for topics the date right off, just to make it
easier to poke through some batches of time later. One tag is for the
year, and another for YYYY-MM format. After that I may add additional
tags that are relevant to that day. So, if I’m not sleeping well, I’ll
mention that, and add a “sleep” tag to the journal entry for the
following day. That way, there is a one-click access to list all the
notes, in all notebooks, with that tag. Some of those notes will be
daily journal entries, which can make finding a bit of a timeline for
that topic easier. Think of when a doctor says, “when did you start
noticing these spots?”&lt;/p&gt;
&lt;p&gt;Markdown format is trivial enough to edit, though I still keep
references bookmarked for when I need to look something up. The
journal entries can also link to notes i’ve started on specific
topics, and of course, vice versa. Images can be linked, just like for
any other notes. I don’t use this often for journal notes, but there
might be the occasional cool mushroom that’s popped up in the yard or
whatever.&lt;/p&gt;
&lt;p&gt;Like any habit, getting it to be a habit is the largest challenge. For
me, that initial jump is in the note creation. Now I have created a
note template, which is a couple keystrokes away. It is named after
today’s date, and gives me a couple prompts for things I might want to
record.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="Journal starting template" src="../images/joplin_journal_template.png" /&gt;
&lt;figcaption&gt;Journal starting template&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;The point of the template is that it makes it &lt;em&gt;easy&lt;/em&gt;, and if it’s
easy, then it is just a small next step to open the day’s new entry as
soon as I sit down at my computer. Now it’s open, and I can just flip
to that window and scribble a quick note because it’s right there.&lt;/p&gt;
&lt;p&gt;I don’t get too far into formal Zettelkasten process, with a ton of
review and reorganizing, but there is a bit. Again, the tool makes it
easy. If I’m blathering on and on in the journal entry about something
that needs its own note, I can simply create one, cut and paste, and
then link to it from the journal. Joplin’s got full-text search, so
finding things is not challenged by my tendency to clutter. Ease of
moving notes and content around means that incremental fixes are
trivial enough to actually do. Sync means I can find my notes on other
devices, and update them or scribble a quick bit on my phone, and
still make sense of it later.&lt;/p&gt;
&lt;p&gt;To the extent that there is a process (open new day, add tags, type
something, link), it’s been a great success. Improvements are
incremental, though rarely needed. If I feel a need, I’ll make a
change to my template, then the improvment continues for later days.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: This is post #3 for&lt;/em&gt;
&lt;a href="https://100daystooffload.com/"&gt;#100DaysToOffload&lt;/a&gt;. Yes, it’s going
slowly. No, I’m not concerned about the one-year period used by
default. My thought here is that it prompts me to do it regularly, not
that the benchmark needs to be that arbitrary.&lt;/p&gt;
    &lt;/section&gt;
    
    &lt;section id="isso-thread"&gt;&lt;/section&gt;
&lt;/article&gt;

        

        &lt;footer&gt;
	    &lt;a href="https://notbyai.fyi/"&gt;&lt;img src="../images/written_by_human.png" /&gt;&lt;/a&gt;
            Site generated using the awesome
            &lt;a href="https://jaspervdj.be/hakyll"&gt;Hakyll&lt;/a&gt;
        &lt;/footer&gt;</description><author>Recursive Ramblings</author><pubDate>Wed, 16 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://fractaldragon.net/posts/2024-10-16-journal-practice-so-far.html</guid></item><item><title>BM25: keyword search</title><link>https://kaszkowiak.org/en/blog/bm25/</link><description>Improve search in your application! Implement BM25: discover how it works, learn about potential issues, and get practical advice 🚀</description><author>Blog on Maciej Kaszkowiak</author><pubDate>Wed, 16 Oct 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://kaszkowiak.org/en/blog/bm25/</guid></item><item><title>Eve 2.2.0</title><link>https://nicolaiarocci.com/eve-2.2.0/</link><description>&lt;p&gt;Today I released &lt;a href="https://pypi.org/project/Eve/2.2.0/"&gt;Eve 2.2&lt;/a&gt;. It is a maintenance release that drops old Pythons and adds support for the latest versions of the language. Long overdue, it also gets rid of some annoying deprecation warnings. As always, see the &lt;a href="https://docs.python-eve.org/en/stable/changelog.html#version-v2-2"&gt;changelog&lt;/a&gt; for details. Many thanks to Bret Curtis and Guillaume Le Pape for their contributions to this release.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Tue, 15 Oct 2024 10:37:02 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/eve-2.2.0/</guid></item><item><title>The Value Is The Process</title><link>https://www.craigpardey.com/post/2024-10-15-value-of-process/</link><description>&lt;blockquote&gt;
&lt;p&gt;Plans are worthless, but planning is everything &amp;ndash; Dwight D. Eisenhower&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The value of a process is primarily derived from the process itself, not the output.&lt;/p&gt;
&lt;p&gt;Producing a slide deck or document on a topic requires hours of thought, multiple drafts and revisions, wordsmithing, and lots of research.&lt;/p&gt;
&lt;p&gt;This iterative process helps to clarify your thinking around a topic. The hours spent thinking deeply about a problem and continuously refining your arguments is where the real value lies, not in the artifact that results from that process.&lt;/p&gt;</description><author>Craig Pardey</author><pubDate>Tue, 15 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.craigpardey.com/post/2024-10-15-value-of-process/</guid></item><item><title>🐰 Elixir Basics: Working with AMQP</title><link>https://james-carr.org/posts/2024-10-15-elixir-basics-working-with-amqp/</link><description>Previously on Elixir Basics, we explored using GenServer to set up multiple workers that would print a message out at a random interval. Today, I&amp;rsquo;m going to expand on it by adding AMQP to the mix, having our workers publish messages on an interval and add a solitary consumer process to consume the messages.
Overview If you recall from last time, we had a simple setup of an application, a supervisor, and N workers.</description><author>James Carr</author><pubDate>Tue, 15 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://james-carr.org/posts/2024-10-15-elixir-basics-working-with-amqp/</guid></item><item><title>Linux Assembly Part 3: Control Flow</title><link>https://cookie.engineer/weblog/articles/linux-assembly-part-3-control-flow.html</link><description>Learn Linux assembly to do function calls.</description><author>Cookie Engineer's Web Log</author><pubDate>Tue, 15 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://cookie.engineer/weblog/articles/linux-assembly-part-3-control-flow.html</guid></item><item><title>2024-10-15</title><link>https://ho.dges.online/pictures/2024-10-15/</link><description/><author>ho.dges.online</author><pubDate>Tue, 15 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-10-15/</guid></item><item><title>What Wiki Wants to Become</title><link>https://one.mikro2nd.net/pages/what-wiki-wants-to-become/</link><description/><author>one mikro2nd</author><pubDate>Tue, 15 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://one.mikro2nd.net/pages/what-wiki-wants-to-become/</guid></item><item><title>On effective communication</title><link>https://avikdas.com/2024/10/15/on-effective-communication.html</link><description>The original logo from Talk it Out</description><author>Avik Das</author><pubDate>Tue, 15 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://avikdas.com/2024/10/15/on-effective-communication.html</guid></item><item><title>How to Make Racket Go (Almost) As Fast As C</title><link>https://lambdaland.org/posts/2024-09-27_threaded_interpreter/</link><description>&lt;p&gt;I recently wrote about &lt;a href="https://lambdaland.org/posts/2024-09-11_parameterized_decisions/"&gt;using first-class functions to help make a BF interpreter&lt;/a&gt;. This is a follow-up post to describe a nifty solution to a tricky problem that made my program go 2–5× faster and put it about on-par with an interpreter written in pure C.&lt;/p&gt;
&lt;p&gt;A basic interpreter works by walking down the AST and evaluating nodes recursively: when the interpreter encounters an expression, it dispatches on the type of expression to decide how to perform the evaluation. Here&amp;rsquo;s the key insight to get a massive speed bump with very little effort: &lt;em&gt;that dispatch is expensive and can be performed ahead-of-time&lt;/em&gt;. We can walk through the code &lt;em&gt;once&lt;/em&gt; and precompute all the dispatching work.&lt;/p&gt;
&lt;p&gt;This is not a new idea. The first description that I&amp;rsquo;m aware of comes from Feeley and Lapalme [&lt;a href="#citeproc_bib_item_1"&gt;1&lt;/a&gt;]. A name for this technique is making a &lt;em&gt;threaded interpreter&lt;/em&gt;. It&amp;rsquo;s nowhere near as fast as a native code compiler, but interpreters are easy to write, and this is a very simple way to get a very big boost in performance.&lt;/p&gt;
&lt;h2 id="a-basic-interpreter"&gt;
  A basic interpreter
  &lt;a class="anchor" href="#a-basic-interpreter"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;Please see &lt;a href="#appendix-full-code-for-basic-interpreter"&gt;the appendix&lt;/a&gt; for the full code for this interpreter. Tested to run with Racket v8.14 [cs].&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Here&amp;rsquo;s a simple language and a (simplified) interpreter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Syntax description&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; func &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;params body&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; app &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;fn-expr args&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; op &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;op-name arg1 arg2&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; if-expr &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;test-expr t-case f-case&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Closure values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; closure &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;params body env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Core interpreter routine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret expr env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;match&lt;/span&gt; expr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #616e87; font-style: italic;"&gt;;; Literal values: booleans and numbers evaluate to themselves&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;or&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;number?&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;boolean?&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; expr&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #616e87; font-style: italic;"&gt;;; Look up variables in the environment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;symbol?&lt;/span&gt; var-name&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;lookup-env env var-name&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #616e87; font-style: italic;"&gt;;; Evaluating a function expression yields a closure value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;func params body&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;closure params body env&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #616e87; font-style: italic;"&gt;;; Application: evaluate the function expression to get a closure.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #616e87; font-style: italic;"&gt;;; Next, evaluate the arguments, and then extend the environment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #616e87; font-style: italic;"&gt;;; and evaluate the body of the closure.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;app fn-expr args&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;fn &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret fn-expr env&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;eval-args &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;map&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;e&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret e env&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; args&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;closure-body fn&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;extend-env env &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;closure-params fn&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; eval-args&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #616e87; font-style: italic;"&gt;;; Built-in operators&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;op op-name a1 a2&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;arg1 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret a1 env&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;arg2 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret a2 env&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;case&lt;/span&gt; op-name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; arg1 arg2&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;else&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;error&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"Undefined operator!"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)]))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #616e87; font-style: italic;"&gt;;; Conditionals&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;if-expr test tcase fcase&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret test env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret tcase env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret fcase env&lt;span style="color: #eceff4;"&gt;))]))&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We can now build and run simple programs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;app &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;func &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;x&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;op &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;+&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;x&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;list&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;41&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; =&amp;gt; 42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;There is nothing particularly special about this interpreter: there&amp;rsquo;s a basic representation for programs, and the &lt;code&gt;interpret&lt;/code&gt; function walks the AST recursively to evaluate the program.&lt;/p&gt;
&lt;h2 id="threading-the-environment"&gt;
  Threading the environment
  &lt;a class="anchor" href="#threading-the-environment"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;To make this interpreter go faster, we need bypass the &lt;code&gt;match&lt;/code&gt; statement by pre-computing the code to run. We can do this by building up a closure that calls the next thing to run in tail-position. Racket has a proper tail-call optimization, so function calls in tail position will be optimized to &lt;code&gt;jump&lt;/code&gt; instructions and they won&amp;rsquo;t grow the stack. Having known jump targets is also really good for modern CPUs which do speculative execution; known jump targets means no branch mis-predictions.&lt;/p&gt;
&lt;p&gt;We do this by breaking up the &lt;code&gt;interpret&lt;/code&gt; function: instead of taking an expression and an environment, we want a function that just takes an expression. This should return a function that we can give an environment, which will the compute the value of the program. We will call this new function &lt;code&gt;compile&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; expr&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;match&lt;/span&gt; expr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;or&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;number?&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;boolean?&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; expr&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;symbol?&lt;/span&gt; var-name&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;lookup-env env var-name&lt;span style="color: #eceff4;"&gt;))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;func params body&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;comp-body &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; body&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;closure params comp-body env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;app fn-expr args&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;fn &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; fn-expr&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;comp-args &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;map&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; args&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;c &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;fn env&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;closure-body c&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;extend-env env &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;closure-params c&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;map&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;a&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;a env&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; comp-args&lt;span style="color: #eceff4;"&gt;))))))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;op op-name a1 a2&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;arg1 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; a1&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;arg2 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; a2&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;case&lt;/span&gt; op-name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;-&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;-&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;*&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;*&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;/&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;/&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;else&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;error&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"Undefined operator!"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)]))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;if-expr test tcase fcase&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;comp-test &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; test&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;comp-tcase &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; tcase&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;comp-fcase &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; fcase&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;comp-test env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;comp-tcase env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;comp-fcase env&lt;span style="color: #eceff4;"&gt;))))]))&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note how the code follows the same structure as the basic interpreter, but the return type has changed: instead of a value, you get a function in the form of &lt;code&gt;(λ (env) …)&lt;/code&gt;. Also, instead of calling &lt;code&gt;interpret&lt;/code&gt; on subexpressions, you call &lt;code&gt;compile&lt;/code&gt;, and pass the environment to those subexpressions to get the value out.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a lot more that we could here to improve things. The easiest thing would be to track where variables will be in the environment and optimize variable lookups with direct jumps into the environment structure. This saves us from having to walk the environment linearly on every variable lookup. I won&amp;rsquo;t implement that here, but that&amp;rsquo;s some pretty low-hanging fruit.&lt;/p&gt;
&lt;h2 id="the-tricky-part-of-bf-mutual-recursion"&gt;
  The tricky part of BF: mutual recursion
  &lt;a class="anchor" href="#the-tricky-part-of-bf-mutual-recursion"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;So, we get a lot of oomph by turning everything into tail calls. But loops (which, in BF, are the only branching mechanism) present a tricky problem: you either need to call the function that encodes the loop body repeatedly &lt;em&gt;or&lt;/em&gt; call the function that encodes whatever comes after the loop. Moreover, once the loop body is done, it needs to jump back to the first function that encodes the choice of whether to keep going in the loop or exit.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s my &lt;code&gt;compile&lt;/code&gt; function for &lt;a href="https://github.com/ashton314/brainfreeze/blob/0a7a5d011c9576a4800edfe56d1e174d5ca638ac/interp_threaded.rkt#L40"&gt;my basic threaded interpreter&lt;/a&gt; for BF:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; program c-ip jmp-targets inst-cache&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;lt;&lt;/span&gt; c-ip &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-length&lt;/span&gt; program&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;match&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-ref&lt;/span&gt; program c-ip&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;#\+&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;rest-progn &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; program &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; c-ip&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; jmp-targets inst-cache&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;               &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-set!&lt;/span&gt; state sp &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-ref&lt;/span&gt; state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;rest-progn state sp&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;#\-&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;rest-progn &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; program &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; c-ip&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; jmp-targets inst-cache&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;               &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-set!&lt;/span&gt; state sp &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;-&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-ref&lt;/span&gt; state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;rest-progn state sp&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;#\&amp;gt;&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;rest-progn &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; program &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; c-ip&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; jmp-targets inst-cache&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;               &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;rest-progn state &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; sp &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;#\&amp;lt;&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;rest-progn &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; program &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; c-ip&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; jmp-targets inst-cache&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;               &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;rest-progn state &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;-&lt;/span&gt; sp &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;#\.&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;rest-progn &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; program &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; c-ip&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; jmp-targets inst-cache&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;               &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;display&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;integer-&amp;gt;char&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-ref&lt;/span&gt; state sp&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;rest-progn state sp&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;#\,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;rest-progn &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; program &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; c-ip&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; jmp-targets inst-cache&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-set!&lt;/span&gt; state sp &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;char-&amp;gt;integer&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;read-char&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;rest-progn state sp&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;jmp-forward target&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;letrec&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;loop-start &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                                &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;zero?&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-ref&lt;/span&gt; state sp&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                                    &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;loop-end state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                                    &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;loop-body state sp&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                  &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;loop-past-end &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; program &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; c-ip target &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; jmp-targets inst-cache&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                  &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;loop-end &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; program &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; c-ip target&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;cons&lt;/span&gt; loop-start loop-past-end&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; inst-cache&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                  &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;loop-body &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; program &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; c-ip&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;null&lt;/span&gt; inst-cache&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           loop-start&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;jmp-backward &lt;span style="color: #81a1c1; font-weight: bold;"&gt;_&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;zero?&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-ref&lt;/span&gt; state sp&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;               &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;cdr&lt;/span&gt; jmp-targets&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; state sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;               &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;car&lt;/span&gt; jmp-targets&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; state sp&lt;span style="color: #eceff4;"&gt;)))])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;_state _sp&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;           &lt;span style="color: #616e87; font-style: italic;"&gt;; finished compiling program&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #81a1c1;"&gt;void&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;I match on each of the characters of the program. In my interpreter I do a little bit of preprocessing to turn &lt;code&gt;[&lt;/code&gt; and &lt;code&gt;]&lt;/code&gt; into &lt;code&gt;jmp-forward&lt;/code&gt; and &lt;code&gt;jmp-backward&lt;/code&gt; structs respectively. That way, I don&amp;rsquo;t have to spend a ton of time scanning the program for matching brackets.&lt;/p&gt;
&lt;p&gt;The interesting bit is the clause for the &lt;code&gt;jmp-forward&lt;/code&gt; construct: to build the closure I need at a &lt;code&gt;loop-start&lt;/code&gt;, I need to be able to refer to the loop body (computed and stored in &lt;code&gt;loop-body&lt;/code&gt;) as well as the end of the loop (computed and stored in &lt;code&gt;loop-end&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;When I build the &lt;code&gt;loop-end&lt;/code&gt; closure, pass &lt;code&gt;loop-start&lt;/code&gt; and &lt;code&gt;loop-past-end&lt;/code&gt; (which is the rest of the program &lt;em&gt;after&lt;/em&gt; the loop) into the &lt;code&gt;compile&lt;/code&gt; function as the &lt;code&gt;jmp-targets&lt;/code&gt; parameter. When the compiler encounters the matching &lt;code&gt;jmp-backward&lt;/code&gt; instruction, it uses the &lt;code&gt;jmp-targets&lt;/code&gt; parameter to get the &lt;code&gt;loop-start&lt;/code&gt; and &lt;code&gt;loop-past-end&lt;/code&gt; to decide whether or not to redo the loop or not.&lt;/p&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;I&amp;rsquo;ve since improved this code, and now the &lt;code&gt;zero?&lt;/code&gt; check only happens once. I also parse the program so that every instruction gets turned into a struct—rather than left as a bare character. See &lt;a href="https://github.com/ashton314/brainfreeze/blob/main/interp_threaded_opt.rkt"&gt;interp_threaded_opt.rkt&lt;/a&gt; in my Brainfreeze repo for the current version.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;loop-start&lt;/code&gt; and &lt;code&gt;loop-end&lt;/code&gt; functions need to reference each other to be able to continue or abort the loop. &lt;code&gt;letrec&lt;/code&gt; lets me build functions that can reference each other in a clean, functional way.&lt;/p&gt;
&lt;h2 id="let-s-see-some-numbers"&gt;
  Let&amp;rsquo;s see some numbers!
  &lt;a class="anchor" href="#let-s-see-some-numbers"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;So how much faster does threading make the code go? &lt;a href="https://github.com/cwfitzgerald/brainfuck-benchmark/blob/2e10658581ce0c81b02e858e292984cf8e5df96a/benches/mandel.b"&gt;Here&lt;/a&gt; is a BF program that renders a Mandelbrot set. I can run this with my basic and my threaded interpreter on my M1 Pro MacBook Pro to get an idea:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;racket interp_basic.rkt bench/benches/mandel.b  43.25s user 0.77s system 93% cpu 47.138 total
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;racket interp_threaded.rkt bench/benches/mandel.b  17.62s user 0.38s system 92% cpu 19.461 total
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;43.25 seconds&lt;/code&gt; vs &lt;code&gt;17.62 seconds&lt;/code&gt; is a big difference! That&amp;rsquo;s a solid 2× speedup! This actually put my threaded Racket interpreter on par with a C-based threaded interpreter that one of my classmates built and ran with the same benchmarks. If I recall, his was only about a second or two faster.&lt;/p&gt;
&lt;p&gt;The compile step opens up a big opportunity for optimizations. I&amp;rsquo;ve been working on some domain-specific optimizations for BF and my interpreter can run the same benchmark in a blazing &lt;code&gt;9.94 seconds&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;racket runner.rkt bench/benches/mandel.b  9.94s user 0.18s system 94% cpu 10.707 total
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(And, of course, none of this really holds a candle to a proper compiler; I&amp;rsquo;ve to a compiler that takes the optimized code from the threaded interpreter and emits machine code. It can run &lt;code&gt;mandel.b&lt;/code&gt; in a mere &lt;code&gt;0.5 seconds&lt;/code&gt;!)&lt;/p&gt;
&lt;p&gt;I hope you take away a few things from this post:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Proper tail-calls make stuff go fast. If your language supports proper tail-call optimization, take advantage of it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Racket is &lt;em&gt;really good&lt;/em&gt; for writing interpreters and compilers! You can get very fast performance in the comfort of a high-level garbage-collected functional programming language.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Racket will never be able to match the best-written C code in terms of speed. But Racket is far easier to debug and a lot more fun to write—and for a lot of applications, Racket is still more than fast enough.&lt;/p&gt;
&lt;div class="epigraph"&gt;
&lt;blockquote&gt;
&lt;p&gt;A bad day writing code in Scheme is better than a good day writing code in C.&lt;/p&gt;
&lt;footer&gt;
&lt;p&gt;David Stigant&lt;/p&gt;
&lt;/footer&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;h2 id="appendix-full-code-for-basic-interpreter"&gt;
  Appendix: Full code for basic interpreter
  &lt;a class="anchor" href="#appendix-full-code-for-basic-interpreter"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;#lang &lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;racket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Syntax description&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; func &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;params body&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; app &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;fn-expr args&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; op &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;op-name arg1 arg2&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; if-expr &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;test-expr t-case f-case&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Closure values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; closure &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;params body env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Environment helpers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;extend-env base-env vars vals&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;foldl&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;var val env-acc&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;cons&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;cons&lt;/span&gt; var val&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; env-acc&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; base-env vars vals&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;lookup-env env var&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;match&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;assoc&lt;/span&gt; var env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;cons&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;_&lt;/span&gt; val&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; val&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;#f&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;error&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"Undefined variable!"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)]))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Core interpreter routine&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret expr env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;match&lt;/span&gt; expr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;or&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;number?&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;boolean?&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; expr&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;symbol?&lt;/span&gt; var-name&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;lookup-env env var-name&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;func params body&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;closure params body env&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;app fn-expr args&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;fn &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret fn-expr env&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;eval-args &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;map&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;e&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret e env&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; args&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;closure-body fn&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;extend-env env &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;closure-params fn&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; eval-args&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;op op-name a1 a2&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;arg1 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret a1 env&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;arg2 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret a2 env&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;case&lt;/span&gt; op-name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; arg1 arg2&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;-&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;-&lt;/span&gt; arg1 arg2&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;*&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;*&lt;/span&gt; arg1 arg2&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;/&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;/&lt;/span&gt; arg1 arg2&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;lt;&lt;/span&gt; arg1 arg2&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;gt;&lt;/span&gt; arg1 arg2&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt; arg1 arg2&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;else&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;error&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"Undefined operator!"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)]))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;if-expr test tcase fcase&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret test env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret tcase env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;interpret fcase env&lt;span style="color: #eceff4;"&gt;))]))&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="appendix-full-code-for-threading-interpreter"&gt;
  Appendix: Full code for threading interpreter
  &lt;a class="anchor" href="#appendix-full-code-for-threading-interpreter"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;#lang &lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;racket&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Syntax description&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; func &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;params body&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; app &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;fn-expr args&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; op &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;op-name arg1 arg2&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; if-expr &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;test-expr t-case f-case&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Closure values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; closure &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;params body env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Environment helpers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;extend-env base-env vars vals&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;foldl&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;var val env-acc&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;cons&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;cons&lt;/span&gt; var val&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; env-acc&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; base-env vars vals&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;lookup-env env var&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;match&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;assoc&lt;/span&gt; var env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;cons&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;_&lt;/span&gt; val&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; val&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #8fbcbb;"&gt;#f&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;error&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"Undefined variable!"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)]))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; Threading interpreter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; expr&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;match&lt;/span&gt; expr
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;or&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;number?&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;boolean?&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; expr&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;? &lt;span style="color: #81a1c1;"&gt;symbol?&lt;/span&gt; var-name&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;lookup-env env var-name&lt;span style="color: #eceff4;"&gt;))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;func params body&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;comp-body &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; body&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;closure params comp-body env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;app fn-expr args&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;fn &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; fn-expr&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;comp-args &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;map&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; args&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;c &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;fn env&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;closure-body c&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;extend-env env &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;closure-params c&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;map&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;a&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;a env&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; comp-args&lt;span style="color: #eceff4;"&gt;))))))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;op op-name a1 a2&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;arg1 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; a1&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;arg2 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; a2&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;case&lt;/span&gt; op-name
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;-&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;-&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;*&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;*&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;/&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;/&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;&amp;gt;&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg1 env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;arg2 env&lt;span style="color: #eceff4;"&gt;)))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;else&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;error&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"Undefined operator!"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)]))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;[(&lt;/span&gt;if-expr test tcase fcase&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;comp-test &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; test&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;comp-tcase &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; tcase&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;           &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;comp-fcase &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;compile&lt;/span&gt; fcase&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;       &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;λ&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;comp-test env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;comp-tcase env&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;comp-fcase env&lt;span style="color: #eceff4;"&gt;))))]))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;;; compute 42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; p1 &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;app &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;func &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;a&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;op &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;+&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;a&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #b48ead;"&gt;41&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="references"&gt;
  References
  &lt;a class="anchor" href="#references"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="csl-bib-body"&gt;
  &lt;div class="csl-entry"&gt;&lt;a id="citeproc_bib_item_1"&gt;&lt;/a&gt;
    &lt;div class="csl-left-margin"&gt;[1]&lt;/div&gt;&lt;div class="csl-right-inline"&gt;Feeley, M. and Lapalme, G. 1987. Using closures for code generation. &lt;i&gt;Computer languages&lt;/i&gt;. 12, 1 (Jan. 1987), 47–66. DOI:&lt;a href="https://doi.org/10.1016/0096-0551(87)90012-9"&gt;https://doi.org/10.1016/0096-0551(87)90012-9&lt;/a&gt;.&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</description><author>Ashton Wiersdorf on Lambda Land</author><pubDate>Tue, 15 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2024-09-27_threaded_interpreter/</guid></item><item><title>A webring</title><link>https://blog.harterrt.com/data-webring.html</link><description>&lt;p&gt;I added this blog to Randy Au's &lt;a href="https://www.counting-stuff.com/its-2024-lets-have-a-webring/"&gt;webring for data writers&lt;/a&gt;.
Take a &lt;a href="/pages/webring.html"&gt;look here&lt;/a&gt;, or below:&lt;/p&gt;
&lt;hr /&gt;
&lt;div id="data-ring-dot-list"&gt;



&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;I'm struggling to describe why I think this is so interesting.&lt;/p&gt;
&lt;p&gt;Most of the writing I've seen about doing data work is misleading.
It misrepresents what data work actually looks like.&lt;/p&gt;
&lt;p&gt;Everyone writing …&lt;/p&gt;</description><author>blog.harterrt.com</author><pubDate>Tue, 15 Oct 2024 01:20:00 GMT</pubDate><guid isPermaLink="true">https://blog.harterrt.com/data-webring.html</guid></item><item><title>Exposure to Art: a MTA Case Study</title><link>https://ztoz.blog/posts/art-proximity/</link><description>&lt;p&gt;Many corporations and agencies allocate a certain amount of their budget towards the creation of public artwork. For instance, New York&amp;rsquo;s Metropolitan Transportation Authority (MTA) has funded nearly 300 permanent art installations since 1982 under the city&amp;rsquo;s &lt;a href="https://www.nyc.gov/site/dclapercentforart/about/about.page"&gt;&amp;ldquo;Percent for Art&amp;rdquo; law&lt;/a&gt;. If we question the effectiveness of the program, we immediately run into problems of measuring the subjective aesthetic value of the creations. However, we may measure their effectiveness by a necessary quality of input: the extent of MTA travellers exposed to art. We propose a &amp;ldquo;weighted proximity&amp;rdquo; metric as a performance indicator and, using NY Open Data, evaluate their scoring as of 1990, 2000, 2010, and 2020. We find the MTA has been increasing the extent of the traveling population exposed to public art each decade, although the rate of increase has dropped over time and many installations &amp;ldquo;reinforce&amp;rdquo; already existing, nearby art installations. We provide a ranked list of 20 sites which, through new art installations, will yield the greatest increase in our weighted proximity metric and find that three of the sites already have art installations since 2021, signaling continued improvement.&lt;/p&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;Public art is &amp;ldquo;work created by artists for places accessible to and used by the public&amp;rdquo; (Backer 2004). The &amp;ldquo;for places&amp;rdquo; in the definition is key, as public art is contrasted to &amp;ldquo;art in public,&amp;rdquo; where the art was not created specifically for the given site. Further, although an art gallery may be accessible at no cost, the &amp;ldquo;place&amp;rdquo; is normally a broader public space such as a park, thoroughfare, train station, library, or bridge.&lt;/p&gt;
&lt;p&gt;As of 2004, the United States has over 350 public art programs supporting thousands of artist&amp;rsquo;s projects. Eighty-one percent of these programs are controlled within a public agency (Backer 2004). Funding for these projects typically comes from a &amp;ldquo;percent for art&amp;rdquo; law which allocates a certain percentage of the construction cost to artwork. The funding agency often influences the art&amp;rsquo;s subject matter and the designated site will impose certain restrictions or inspirations. For example, a recent prospectus for public art at the &lt;a href="https://artist.callforentry.org/festivals_unique_info.php?ID=14233"&gt;Roosevelt Science Center in New Mexico&lt;/a&gt; specifies the art will be two-dimensional, fit within certain specified areas of the building, and feature a theme that reflects the &amp;ldquo;nature of the building&amp;rsquo;s science-focused focus.&amp;rdquo; Artwork for the public in motion may carry safety requirements, such as not distracting drivers (Wilkerson 2009).&lt;/p&gt;
&lt;p&gt;Since public art is usually publicly funded, represents an increase to the cost of construction or rehabilitation, and sometimes controversial, defenders have argued for the benefits of public art. (Backer 2004) summarizes the benefits using four categories: 1) civic dialogue and community engagement 2) increased attention and economic activity 3) connection of artists with communities and 4) enhancement of public appreciation of art. A more recent survey by (Cheung 2022) categorized benefits along eight themes: Placemaking, Society, Culture, Economy, Sustainability, Wellbeing, Wisdom, and Innovation.&lt;/p&gt;
&lt;p&gt;Although many of these stated benefits are qualitative, we believe it is uncontroversial that the public is intended to be exposed to public art, so the choice of locations for public art should be influenced by where the public happens to be. The pairing of the public&amp;rsquo;s location and the art location is an input to the &amp;ldquo;attract attention and economic benefit&amp;rdquo; from the first survey and the &amp;ldquo;placemaking&amp;rdquo; and &amp;ldquo;wellbeing&amp;rdquo; benefits of the second survey above.&lt;/p&gt;
&lt;h2 id="weighted-proximity-score"&gt;Weighted Proximity Score&lt;/h2&gt;
&lt;p&gt;We model exposure to art by the proximity, &lt;em&gt;p&lt;/em&gt;, of travellers to an art installation. &lt;em&gt;p&lt;/em&gt; is defined as:&lt;/p&gt;
&lt;p&gt;$$ p=e^{-d/100} $$&lt;/p&gt;
&lt;p&gt;where &lt;em&gt;d&lt;/em&gt; is the distance between an anchor point (e.g. a stop or station) and the art installation, in meters. Proximity is modeled as an exponential decay where approximately 2/3rds of the value is within the first 100 meters or one approximate city block. As travellers move beyond a city block, we model that the probability of their exposure to an art piece rapidly declines. Computationally, distances of 500m or more can be treated as zero to avoid numerical issues.&lt;/p&gt;



&lt;figure&gt;
  
  &lt;img alt="Proximity goes from 1 at 0 distance to 0.37 at 100m and 0.05 at 300m" class="fit-image" src="https://ztoz.blog/posts/art-proximity/fig-proximity.png" /&gt;
  
  &lt;figcaption&gt;Figure: Proximity (p) against distance (m)&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;For a given anchor point, the aggregate proximity, &lt;em&gt;P&lt;/em&gt;, is the clamped sum of &lt;em&gt;p&lt;/em&gt;, or:&lt;/p&gt;
&lt;p&gt;$$ P=min(\sum{p}, 1) $$&lt;/p&gt;
&lt;p&gt;By clamping the value of &lt;em&gt;P&lt;/em&gt;, this model implies that at some point there is &amp;ldquo;enough&amp;rdquo; art and installing more will not increase the value.&lt;/p&gt;
&lt;p&gt;Since travellers do not visit anchor points with equal probability, we assign a weight, &lt;em&gt;w&lt;/em&gt;, to each anchor based on the proportion of visits. For this study, we computed the weight as the sum of the average ridership estimate in 2023 based on the &amp;ldquo;MTA Subway Origin-Destination Ridership Estimate&amp;rdquo; data set divided by the total average ridership estimate. The product of &lt;em&gt;P&lt;/em&gt; and &lt;em&gt;w&lt;/em&gt; gives the weighted proximity score:&lt;/p&gt;
&lt;p&gt;$$ P_w=Pw $$&lt;/p&gt;
&lt;h2 id="mta-performance-1990---2020"&gt;MTA Performance, 1990 - 2020&lt;/h2&gt;
&lt;p&gt;The &amp;ldquo;MTA Permanent Art Catalog&amp;rdquo; lists art installations within their network. The first artwork is &lt;a href="https://new.mta.info/agency/arts-design/collection/masstransiscope"&gt;Masstransiscope&lt;/a&gt;, installed in 1980. Installations were slow to start but by 1990 we see a steady flow. (Note that we are treating each row in the catalog as an installation. However, the catalog has repeated instances when installations span multiple stops.)&lt;/p&gt;



&lt;figure&gt;
  
  &lt;img alt="Installations from 1980 to 2024, averaging 5 per year, peak of 38 in 2018" class="fit-image" src="https://ztoz.blog/posts/art-proximity/fig-art-installations-by-year.png" /&gt;
  
  &lt;figcaption&gt;Figure: MTA Art Installations by Year&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;When we plot art installations by location, grouped by decade, we see the definite shape of the transit network becoming visible as art is installed.&lt;/p&gt;
&lt;figure&gt;
	&lt;table&gt;
		&lt;tbody&gt;
			&lt;tr&gt;
				&lt;td style="padding: 0; border: 0;"&gt;&lt;img src="fig-art-locations-1990.png" width="100%" /&gt;&lt;/td&gt;
				&lt;td style="padding: 0; border: 0;"&gt;&lt;img src="fig-art-locations-2000.png" width="100%" /&gt;&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
				&lt;td style="padding: 0; border: 0;"&gt;&lt;img src="fig-art-locations-2010.png" width="100%" /&gt;&lt;/td&gt;
				&lt;td style="padding: 0; border: 0;"&gt;&lt;img src="fig-art-locations-2020.png" width="100%" /&gt;&lt;/td&gt;
			&lt;/tr&gt;
		&lt;/tbody&gt;
	&lt;/table&gt;
	&lt;caption&gt;Figure: Art Installation Locations as of Year&lt;/caption&gt;
&lt;/figure&gt; 	
&lt;p&gt;Although some art is installed along a transit line, the majority of pieces are located within a subway station, near a stop, or near an MTA complex. Calculating the proximity score for each complex (using the list of complexes from the 2023 Origin-Destination dataset), we see that each decade has seen a strong decline in the number of sites with effectively zero proximity to art, from 94% in 1990 to 38% in 2020. The mean &lt;em&gt;p&lt;/em&gt; score has increased from 0.05 in 1990 to 0.64 in 2020.&lt;/p&gt;



&lt;figure&gt;
  
  &lt;img alt="Proximity scores of 0 decline rapidly, by 2020 " class="fit-image" src="https://ztoz.blog/posts/art-proximity/fig-proximity-dist-by-decade.png" /&gt;
  
  &lt;figcaption&gt;Figure: Distribution of Proximity (p) to MTA Complexes by Decade&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;As of Year&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Mean Proximity (p) Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1990&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.05&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;2000&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;2010&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;2020&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.64&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;To weight the locations by population, we use the MTA&amp;rsquo;s Origin Destination dataset and sum each location&amp;rsquo;s weight by each origin destination pair&amp;rsquo;s average ridership data. To validate this approach, we compare &lt;a href="https://new.mta.info/agency/new-york-city-transit/subway-bus-ridership-2023"&gt;MTA&amp;rsquo;s summary of 2023 ridership&lt;/a&gt; by our weights. The proportion of ridership of each location versus the 10th ranked position has good alignment.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rank&lt;/th&gt;
&lt;th&gt;Station/complex&lt;/th&gt;
&lt;th&gt;Ridership&lt;/th&gt;
&lt;th&gt;Ridership vs. 10th&lt;/th&gt;
&lt;th&gt;Raw Weight&lt;/th&gt;
&lt;th&gt;Raw Weight vs. 10th&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Times Sq-42 St/Port Authority Bus Terminal&lt;/td&gt;
&lt;td&gt;54,266,441&lt;/td&gt;
&lt;td&gt;3.91&lt;/td&gt;
&lt;td&gt;19,703,290&lt;/td&gt;
&lt;td&gt;3.12&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Grand Central-42 St&lt;/td&gt;
&lt;td&gt;30,517,475&lt;/td&gt;
&lt;td&gt;2.20&lt;/td&gt;
&lt;td&gt;13,926,937&lt;/td&gt;
&lt;td&gt;2.21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;34 St-Herald Sq&lt;/td&gt;
&lt;td&gt;23,680,977&lt;/td&gt;
&lt;td&gt;1.71&lt;/td&gt;
&lt;td&gt;11,000,955&lt;/td&gt;
&lt;td&gt;1.74&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;14 St-Union Sq&lt;/td&gt;
&lt;td&gt;21,527,757&lt;/td&gt;
&lt;td&gt;1.55&lt;/td&gt;
&lt;td&gt;10,301,633&lt;/td&gt;
&lt;td&gt;1.63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Fulton St&lt;/td&gt;
&lt;td&gt;17,887,203&lt;/td&gt;
&lt;td&gt;1.29&lt;/td&gt;
&lt;td&gt;8,325,765&lt;/td&gt;
&lt;td&gt;1.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;34 St-Penn Station&lt;/td&gt;
&lt;td&gt;16,974,543&lt;/td&gt;
&lt;td&gt;1.22&lt;/td&gt;
&lt;td&gt;7,652,911&lt;/td&gt;
&lt;td&gt;1.21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;59 St-Columbus Circle&lt;/td&gt;
&lt;td&gt;15,842,348&lt;/td&gt;
&lt;td&gt;1.14&lt;/td&gt;
&lt;td&gt;7,506,844&lt;/td&gt;
&lt;td&gt;1.19&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;34 St-Penn Station&lt;/td&gt;
&lt;td&gt;15,224,047&lt;/td&gt;
&lt;td&gt;1.10&lt;/td&gt;
&lt;td&gt;6,914,365&lt;/td&gt;
&lt;td&gt;1.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Jackson Hts-Roosevelt Av/74 St-Broadway&lt;/td&gt;
&lt;td&gt;14,348,691&lt;/td&gt;
&lt;td&gt;1.03&lt;/td&gt;
&lt;td&gt;6,555,554&lt;/td&gt;
&lt;td&gt;1.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Flushing-Main St&lt;/td&gt;
&lt;td&gt;13,876,213&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;td&gt;6,310,435&lt;/td&gt;
&lt;td&gt;1.00&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Weights decline rapidly and the median weight is 732,000. Half of the ridership is accounted for in the top 63 (of 425) sites. When we plot the locations and color them based on their weight, we can see most locations have a dark blue/purple value. Thus, per normal power law relations, a significant percentage of travellers can be exposed to art with art installations at a fraction of stations.&lt;/p&gt;
&lt;div&gt;                        
                        &lt;div class="plotly-graph-div" id="fee6078b-1220-49b9-a22f-4b48a6f3bc46" style="height: 100%; width: 100%;"&gt;&lt;/div&gt;                    &lt;/div&gt;
&lt;p&gt;Using these weights and using the proximity scores for each site by decade, we can calculate the weighted proximity, or \(P_w\) value, for each site. Grouping them by decade, we calculate:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;As of Year&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Mean Weighted Proximity (\(P_w\)) Score&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1990&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.0002&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;2000&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.0007&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;2010&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.0012&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;2020&lt;/td&gt;
&lt;td style="text-align: right;"&gt;0.0014&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Public exposure to art, as measured by \(P_w\), has increased every decade, but the marginal increase has slowed. Using the 2020 data (see table below), about half of the weight of travellers has a &lt;em&gt;p&lt;/em&gt; score greater than 0.8, indicating close proximity to public art. A quarter of the weight have some public art within 500m, but more than 25m. The last quarter are more than 500m from public art.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Criteria&lt;/th&gt;
&lt;th style="text-align: right;"&gt;# of Sites (2020)&lt;/th&gt;
&lt;th style="text-align: right;"&gt;% of Weight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;p &amp;gt; 1&lt;/td&gt;
&lt;td style="text-align: right;"&gt;94&lt;/td&gt;
&lt;td style="text-align: right;"&gt;31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;p &amp;gt; 0.8&lt;/td&gt;
&lt;td style="text-align: right;"&gt;220&lt;/td&gt;
&lt;td style="text-align: right;"&gt;53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;0.8 &amp;gt; p &amp;gt; 0&lt;/td&gt;
&lt;td style="text-align: right;"&gt;74&lt;/td&gt;
&lt;td style="text-align: right;"&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;p = 0&lt;/td&gt;
&lt;td style="text-align: right;"&gt;131&lt;/td&gt;
&lt;td style="text-align: right;"&gt;22&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The slowdown in the increase may be attributed to some sites hosting multiple art pieces. There are 94 sites with more than one public artwork. This model limits &lt;em&gt;P&lt;/em&gt; to at most one maximal proximity piece, so having two pieces in close proximity does not double the exposure.&lt;/p&gt;
&lt;h2 id="highest-impact-locations-by-2030"&gt;Highest Impact Locations by 2030&lt;/h2&gt;
&lt;p&gt;If the MTA wished to increase their \(P_w\) score for 2030, they should install art within sites with the highest remaining impact, namely sites without existing art but a high weight. (We are assuming no artwork will be removed from an existing site.)&lt;/p&gt;
&lt;h3 id="comparison-to-mtas-2021-2023-art-installations"&gt;Comparison to MTA&amp;rsquo;s 2021-2023 Art Installations&lt;/h3&gt;
&lt;p&gt;The MTA permanent art catalog lists 23 installations between 2021 and 2023 (below). Some of these sites, such as the two Mount Vernon bridges and the Roosevelt Island Ventilation Structure (&amp;ldquo;Double Take&amp;rdquo;), represent installations visible along transit, versus installed at a stop. Note that the model does not include any anchor points along the transit routes, so the model does not value installations of this type.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Agency&lt;/th&gt;
&lt;th style="text-align: left;"&gt;2021-2023 Installation Site&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;86 St&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;138 St-Grand Concourse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Metro-North&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Port Jervis&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Metro-North&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Mount Vernon 3rd Avenue Bridge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;1 Av&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Bedford Av&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Metro-North&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Mount Vernon 10th Avenue Bridge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;LIRR&lt;/td&gt;
&lt;td style="text-align: left;"&gt;New Hyde Park&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;LIRR&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Westbury&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Tremont Av&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;LIRR&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Grand Central Madison&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Times Sq-42 St&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;LIRR&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Elmont-UBS Arena&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;LIRR&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Grand Central Madison&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;181 St&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Lorimer St&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;7 Av&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;34 St-Penn Station&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;LIRR&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Roosevelt Island Ventilation Structure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;LIRR&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Mineola&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Grand St&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;NYCT&lt;/td&gt;
&lt;td style="text-align: left;"&gt;E 149 St&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;SIR&lt;/td&gt;
&lt;td style="text-align: left;"&gt;New Dorp&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Based on a stop/complex&amp;rsquo;s weight and the lack of public art in the proximity, we can rank the top 20 sites where the addition of public art will yield the greatest increase in MTA&amp;rsquo;s \(P_w\) score. Of this list, the MTA has installed art since 2020 at three of the twenty locations. (As of this writing, the data does not cover 2024 or later.)&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Complex ID&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Recommended Site&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Art Installation Since 2020?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;120&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Bedford Av (L)&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;451&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Junction Blvd (7)&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;450&lt;/td&gt;
&lt;td style="text-align: left;"&gt;103 St-Corona Plaza (7)&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;231&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Grand St (B,D)&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;119&lt;/td&gt;
&lt;td style="text-align: left;"&gt;1 Av (L)&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;261&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Forest Hills-71 Av (E,F,M,R)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;153&lt;/td&gt;
&lt;td style="text-align: left;"&gt;125 St (A,C,B,D)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;605&lt;/td&gt;
&lt;td style="text-align: left;"&gt;168 St (A,C,1)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;259&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Kew Gardens-Union Tpke (E,F)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;151&lt;/td&gt;
&lt;td style="text-align: left;"&gt;145 St (A,C,B,D)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;165&lt;/td&gt;
&lt;td style="text-align: left;"&gt;23 St (C,E)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;452&lt;/td&gt;
&lt;td style="text-align: left;"&gt;90 St-Elmhurst Av (7)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;232&lt;/td&gt;
&lt;td style="text-align: left;"&gt;2 Av (F)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;51&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Kings Hwy (B,Q)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;264&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Woodhaven Blvd (M,R)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;35&lt;/td&gt;
&lt;td style="text-align: left;"&gt;59 St (N,R)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;254&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Jamaica-179 St (F)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;265&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Grand Av-Newtown (M,R)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;234&lt;/td&gt;
&lt;td style="text-align: left;"&gt;East Broadway (F)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;312&lt;/td&gt;
&lt;td style="text-align: left;"&gt;79 St (1)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Note that this list is based solely on proximity to MTA&amp;rsquo;s permanent artworks and the number of passengers; some sites may not be appropriate locations for art due to legal restrictions, lack of percent funding, physical restrictions, or prominent non-MTA artwork.&lt;/p&gt;
&lt;h2 id="limitations-of-approach-and-data"&gt;Limitations of Approach and Data&lt;/h2&gt;
&lt;p&gt;This study&amp;rsquo;s effectiveness is limited by both elements of the approach and of the data. First, our identification of art locations is based solely on the MTA&amp;rsquo;s permanent art catalog and our semi-manual mapping to latitudes and longitudes. This catalog is not  inclusive of the range of MTA&amp;rsquo;s artwork; MTA&amp;rsquo;s Art &amp;amp; Design program also includes digital art, photography exhibitions, posters, poetry, and live music. These latter forms may not necessarily fulfill the definition of &amp;ldquo;public art,&amp;rdquo; rather often being &amp;ldquo;art in public,&amp;rdquo; but the public is being exposed to forms of art. It is up to the viewer how much stress should be placed on &amp;ldquo;public art&amp;rdquo; versus &amp;ldquo;art in public.&amp;rdquo; MTA travellers will also be exposed to art along transit lines and directly outside stops that are not controlled by the MTA and thus not catalogued. Ideally, the MTA should not be obliged to install artwork in an area already well-serviced by other parties.&lt;/p&gt;
&lt;p&gt;The study&amp;rsquo;s method for weighting locations with passenger volumes is based on a data source and an origin-destination model for 2023. Ideally, we would use historical passenger volumes when computing historical &lt;em&gt;P&lt;/em&gt; values.&lt;/p&gt;
&lt;p&gt;The correlation between proximity and exposure may be poor; it is certainly unmeasured. A traveller may not notice an artwork depending on the layout of the station and the flow of traffic. Over time, artwork may also blend into the background due to changes at the site or public familiarity. Temporary or rotating art may yield greater exposure due to its novelty. We expect a series of traveller surveys would illuminate what artwork is being seen and remembered by travellers.&lt;/p&gt;
&lt;h2 id="production-notes"&gt;Production Notes&lt;/h2&gt;
&lt;p&gt;We used &lt;a href="https://duckdb.org/"&gt;DuckDB&lt;/a&gt; for the majority of the data analytics and &lt;a href="https://www.python.org"&gt;Python&lt;/a&gt; for &amp;ldquo;glue&amp;rdquo; and specialized purposes. Visualizations were created using &lt;a href="https://plotly.com/"&gt;plotly&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Primary data sources used for this study:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://data.ny.gov/Transportation/MTA-Permanent-Art-Catalog-Beginning-1980/4y8j-9pkd"&gt;MTA Permanent Art Catalog: Beginning 1980&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://data.ny.gov/Transportation/MTA-Subway-Origin-Destination-Ridership-Estimate-2/uhf3-t34z/about_data"&gt;MTA Subway Origin-Destination Ridership Estimate: 2023&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Secondary data sources used for this study:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://data.ny.gov/Transportation/MTA-Subway-Stations-and-Complexes/5f5g-n3cz/about_data"&gt;MTA Subway Stations and Complexes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://data.ny.gov/Transportation/MTA-General-Transit-Feed-Specification-GTFS-Static/fgm6-ccue/about_data"&gt;MTA General Transit Feed Specification (GTFS) Static Data&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="art-locations"&gt;Art Locations&lt;/h3&gt;
&lt;p&gt;The MTA Permanent Art Catalog identifies the locations of artwork by agency, station name, and line. For the model, we need a latitude and longitude. To derive a latitude and longitude, we attempt to map the agency, station name, and line to a singular location through the secondary data sources. However, since station names and lines change, and are not always represented in a canonical form, this is a messy join process. The &lt;a href="https://gitlab.com/jeffrey_starr/mta-2024-challenge/-/blob/master/namemunging.py"&gt;namemunging.py&lt;/a&gt; script describes the processing steps we took to create the &amp;ldquo;lookup.csv&amp;rdquo; file. The &lt;a href="https://gitlab.com/jeffrey_starr/mta-2024-challenge/-/blob/master/data/corrections.csv"&gt;corrections.csv&lt;/a&gt; contains the manual corrections used to associate art with locations when the location metadata did not match any of the current locations.&lt;/p&gt;
&lt;h2 id="disclaimer"&gt;Disclaimer&lt;/h2&gt;
&lt;p&gt;Although this study leveraged open data provided by the MTA and the New York government, the study was conducted independently of those parties.&lt;/p&gt;
&lt;h1 id="references"&gt;References&lt;/h1&gt;
&lt;p&gt;(Backer 2004) Backer, Jack. 2004. “Public Art: An Essential Component of Creating Communities.” Americans for the Arts Monograph. Americans for the Arts. &lt;a href="https://www.americansforthearts.org/by-program/reports-and-data/legislation-policy/naappd/monograph-public-art-an-essential-component-of-creating-communities"&gt;https://www.americansforthearts.org/by-program/reports-and-data/legislation-policy/naappd/monograph-public-art-an-essential-component-of-creating-communities&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Cheung 2022) Cheung, Ming, Natasha Smith, and Owen Craven. 2022. “The Impacts of Public Art on Cities, Places and People’s Lives.” The Journal of Arts Management, Law, and Society 52 (1): 37–50. &lt;a href="https://doi.org/10.1080/10632921.2021.1942361"&gt;https://doi.org/10.1080/10632921.2021.1942361&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Wilkerson 2009) Wilkerson, April. 2009. “What Becomes Public Art? How Oklahoma Officials Determine What Visually Impacts Locals, Newcomers.” Journal Record, September 8, 2009. Gale General OneFile.&lt;/p&gt;</description><author>ℤ→ℤ</author><pubDate>Mon, 14 Oct 2024 21:55:44 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/art-proximity/</guid></item><item><title>GitLab Pages with multiple domains</title><link>https://nathanfriend.com/2024/10/14/gitab-pages-with-multiple-domains.html</link><description>I recently migrated the website you are on now from AWS to GitLab Pages.</description><author>Nathan Friend</author><pubDate>Mon, 14 Oct 2024 15:41:01 GMT</pubDate><guid isPermaLink="true">https://nathanfriend.com/2024/10/14/gitab-pages-with-multiple-domains.html</guid></item><item><title>Viva Las Vegas: Bruno Mars</title><link>https://digitalnomadder.micro.blog/2024/10/14/viva-las-vegas.html</link><description>&lt;img alt="" height="600" src="https://cdn.uploads.micro.blog/79953/2024/img-0150.jpeg" width="600" /&gt;
&lt;p&gt;We were back in Las Vegas a second time in September to see Bruno Mars at Dolby Live.  It was a great concert.  He is a talented singer, the choreography was great, and he is also a wonderful songwriter.&lt;/p&gt;
&lt;p&gt;I don’t have any pictures of the concert since they made everyone put their phones in a  locked bag.&lt;/p&gt;
&lt;p&gt;The best description of the concert can be found &lt;a href="https://www.ebony.com/we-got-a-seat-inside-bruno-mars-no-phones-las-vegas-residency-and-nightclub-heres-what-went-down/"&gt;here&lt;/a&gt;&lt;/p&gt;</description><author>The Digital Nomad</author><pubDate>Mon, 14 Oct 2024 08:34:59 GMT</pubDate><guid isPermaLink="true">https://digitalnomadder.micro.blog/2024/10/14/viva-las-vegas.html</guid></item><item><title>The Fedora Linux starter pack: everything you need for a smooth experience</title><link>https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;I like &lt;a href="https://fedoraproject.org/"&gt;Fedora Linux.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s the Linux distro that stopped my habit of &lt;a href="https://en.wiktionary.org/wiki/distro-hopping"&gt;distro-hopping.&lt;/a&gt; Big
deal? Ooh, big deal!&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s stable in day-to-day use, ships with up-to-date software, and the software selection is adequate out of the box.&lt;/p&gt;
&lt;p&gt;It also ships with a fresh Linux kernel version&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;, resulting in a good experience even on modern hardware.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve been happily using it for about 4 years at this point, both on personal and work computers.&lt;/p&gt;
&lt;p&gt;However, every time I recommend it to my friends, family and colleagues, I have to mention a few things that a new
Fedora Linux user might want to do first so that the experience is as smooth as possible.&lt;/p&gt;
&lt;p&gt;If it weren&amp;rsquo;t for those aspects, Fedora would be pretty much perfect.&lt;/p&gt;
&lt;h3 id="set-up-rpm-fusion"&gt;Set up RPM Fusion&lt;/h3&gt;
&lt;p&gt;There are some packages that are not available on Fedora by default, likely as a result of copyright laws, lawyers and
Red Hat (IBM) not having the appetite to fight it out in the courts.&lt;/p&gt;
&lt;p&gt;This is why &lt;a href="https://rpmfusion.org/"&gt;RPM Fusion&lt;/a&gt; exists:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;RPM Fusion provides software that the Fedora Project or Red Hat doesn&amp;rsquo;t want to ship.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The installation instructions are &lt;a href="https://rpmfusion.org/Configuration"&gt;here&lt;/a&gt;, and I personally prefer this one-liner
setup:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo dnf install https://mirrors.rpmfusion.org/free/fedora/rpmfusion-free-release-$(rpm -E %fedora).noarch.rpm https://mirrors.rpmfusion.org/nonfree/fedora/rpmfusion-nonfree-release-$(rpm -E %fedora).noarch.rpm
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This step is a prerequisite to the next one.&lt;/p&gt;
&lt;h3 id="enable-hardware-accelerated-video-playback"&gt;Enable hardware accelerated video playback&lt;/h3&gt;
&lt;p&gt;This is the part where I blame lawyers.&lt;/p&gt;
&lt;p&gt;To have a good chance at smoothly playing back videos in a browser or using VLC, you have to do a few extra steps,
because someone fucked up.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://rpmfusion.org/Howto/Multimedia"&gt;Check this guide for up-to-date information.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Switch to the full version of &lt;code&gt;ffmpeg&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo dnf swap ffmpeg-free ffmpeg --allowerasing&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AMD GPU users:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo dnf swap mesa-va-drivers mesa-va-drivers-freeworld&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo dnf swap mesa-vdpau-drivers mesa-vdpau-drivers-freeworld&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Intel GPU users (Broadwell and newer, 2014+):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo dnf install intel-media-driver&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Old Intel GPU users (Ivy Bridge and older, &amp;lt;=2013):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sudo dnf install libva-intel-driver&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;NVIDIA GPU users: good luck.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;You can verify the enabled profiles using &lt;code&gt;vainfo&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the output on my machine before completing the steps (AMD GPU).&lt;/p&gt;
&lt;p&gt;Note the lack of H.264 and HEVC profiles.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;me@mymachine:~$ vainfo
Trying display: wayland
libva info: VA-API version 1.21.0
libva info: Trying to open /usr/lib64/dri-nonfree/radeonsi_drv_video.so
libva info: Trying to open /usr/lib64/dri-freeworld/radeonsi_drv_video.so
libva info: Trying to open /usr/lib64/dri/radeonsi_drv_video.so
libva info: Found init function __vaDriverInit_1_21
libva info: va_openDriver() returns 0
vainfo: VA-API version: 1.21 (libva 2.21.0)
vainfo: Driver version: Mesa Gallium driver 24.1.7 for AMD Radeon 780M (radeonsi, gfx1103_r1, LLVM 18.1.6, DRM 3.57, 6.10.8-200.fc40.x86_64)
vainfo: Supported profile and entrypoints
      VAProfileJPEGBaseline           :	VAEntrypointVLD
      VAProfileVP9Profile0            :	VAEntrypointVLD
      VAProfileVP9Profile2            :	VAEntrypointVLD
      VAProfileAV1Profile0            :	VAEntrypointVLD
      VAProfileAV1Profile0            :	VAEntrypointEncSlice
      VAProfileNone                   :	VAEntrypointVideoProc
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Here&amp;rsquo;s what it looks like after the tweaks are applied.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;me@mymachine:~$ vainfo
Trying display: wayland
libva info: VA-API version 1.21.0
libva info: Trying to open /usr/lib64/dri-nonfree/radeonsi_drv_video.so
libva info: Trying to open /usr/lib64/dri-freeworld/radeonsi_drv_video.so
libva info: Trying to open /usr/lib64/dri/radeonsi_drv_video.so
libva info: Found init function __vaDriverInit_1_21
libva info: va_openDriver() returns 0
vainfo: VA-API version: 1.21 (libva 2.21.0)
vainfo: Driver version: Mesa Gallium driver 24.1.7 for AMD Radeon 780M (radeonsi, gfx1103_r1, LLVM 18.1.6, DRM 3.57, 6.10.8-200.fc40.x86_64)
vainfo: Supported profile and entrypoints
      VAProfileH264ConstrainedBaseline:	VAEntrypointVLD
      VAProfileH264ConstrainedBaseline:	VAEntrypointEncSlice
      VAProfileH264Main               :	VAEntrypointVLD
      VAProfileH264Main               :	VAEntrypointEncSlice
      VAProfileH264High               :	VAEntrypointVLD
      VAProfileH264High               :	VAEntrypointEncSlice
      VAProfileHEVCMain               :	VAEntrypointVLD
      VAProfileHEVCMain               :	VAEntrypointEncSlice
      VAProfileHEVCMain10             :	VAEntrypointVLD
      VAProfileHEVCMain10             :	VAEntrypointEncSlice
      VAProfileJPEGBaseline           :	VAEntrypointVLD
      VAProfileVP9Profile0            :	VAEntrypointVLD
      VAProfileVP9Profile2            :	VAEntrypointVLD
      VAProfileAV1Profile0            :	VAEntrypointVLD
      VAProfileAV1Profile0            :	VAEntrypointEncSlice
      VAProfileNone                   :	VAEntrypointVideoProc
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="enable-flathub"&gt;Enable Flathub&lt;/h3&gt;
&lt;p&gt;This step is only necessary if you did not enable &lt;a href="https://github.com/minosimo"&gt;third-party repositories&lt;/a&gt; during the
inital setup of your Fedora Linux installation.&lt;/p&gt;
&lt;p&gt;Setting up Flathub is just one command:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Check &lt;a href="https://flathub.org/setup/Fedora"&gt;the Flathub setup instructions for Fedora&lt;/a&gt; for up-to-date info.&lt;/p&gt;
&lt;h3 id="-specific-to-estonia-id-card-support"&gt;🇪🇪 Specific to Estonia: ID card support&lt;/h3&gt;
&lt;p&gt;Estonia has &lt;a href="https://www.id.ee/en/"&gt;an ID Card system&lt;/a&gt; (&lt;em&gt;Eesti ID kaart&lt;/em&gt;), and the desktop application is
also officially supported on Linux. &lt;em&gt;&lt;strong&gt;Ubuntu&lt;/strong&gt;&lt;/em&gt; Linux.&lt;/p&gt;
&lt;p&gt;To use the ID card in Fedora, you&amp;rsquo;ll need to install the necessary packages:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sudo dnf install -y open-eid&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Firefox users will need to make sure that the Web eID extension is installed. If
it wasn&amp;rsquo;t installed with the above
command, &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/web-eid-webextension/"&gt;you can also get it here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The desktop application used for opening and digitally signing documents, DigiDoc4, has a bad habit of breaking
sometimes due to changes. Luckily there exists an unofficial version of DigiDoc4 as a Flatpak
and &lt;a href="https://flathub.org/apps/ee.ria.qdigidoc4"&gt;it&amp;rsquo;s available on Flathub.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s mentioned
in &lt;a href="https://github.com/open-eid/linux-installer/wiki/Linux-Packages"&gt;the official open-eid/linux-installer repo&lt;/a&gt; as
well, which lends some credibility (plus I know
the person that built it).&lt;/p&gt;
&lt;p&gt;As of 2024-10-14, ID card works on Firefox and the DigiDoc4 client from the Fedora official repository does not crash
and burn.&lt;/p&gt;
&lt;h3 id="gnome-fix-the-alttab-behaviour"&gt;GNOME: fix the &amp;ldquo;Alt+Tab&amp;rdquo; behaviour&lt;/h3&gt;
&lt;p&gt;GNOME is a great desktop environment, but it, too, has its quirks.&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re used to &amp;ldquo;Alt+Tab&amp;rdquo; keyboard shortcut simply switching windows, then you&amp;rsquo;ll have to tweak it in settings.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Open &amp;ldquo;Settings&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Navigate to &amp;ldquo;Keyboard&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Click on &amp;ldquo;View and Customize shortcuts&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Use the search bar to:
&lt;ul&gt;
&lt;li&gt;disable &amp;ldquo;Switch applications&amp;rdquo; shortcut&lt;/li&gt;
&lt;li&gt;set the &amp;ldquo;Switch windows&amp;rdquo; shortcut to &amp;ldquo;Alt+Tab&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here&amp;rsquo;s what it looks like on GNOME 46.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/shortcuts-1.png"&gt;
        &lt;img alt="Navigate to 'Keyboard' and click on 'View and Customize shortcuts'" height="642" src="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/shortcuts-1_hu_298da5cff19977.png" style="width: auto; height: auto; border-radius: 8px;" width="979" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Navigate to 'Keyboard' and click on 'View and Customize shortcuts'
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/shortcuts-2.png"&gt;
        &lt;img alt="Select 'Navigation'" height="642" src="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/shortcuts-2_hu_bbf5ad27e754359b.png" style="width: auto; height: auto; border-radius: 8px;" width="979" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Select 'Navigation'
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/shortcuts-3.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/shortcuts-3_hu_827adee8c6e68be.png"
             width="979"
             height="642"
             alt="Disable "Switch applications" shortcut."
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Disable "Switch applications" shortcut.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/shortcuts-4.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/shortcuts-4_hu_3058b9e26c280b0c.png"
             width="979"
             height="642"
             alt="Set the "Switch windows" shortcut to "Alt&amp;#43;Tab"."
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Set the "Switch windows" shortcut to "Alt&amp;#43;Tab".
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;h3 id="gnome-minimize-maximize-buttons-on-windows"&gt;GNOME: minimize, maximize buttons on windows&lt;/h3&gt;
&lt;p&gt;By default, you&amp;rsquo;ll only see the &amp;ldquo;Close&amp;rdquo; button on windows, but it is possible to also show minimize and maximize
buttons.&lt;/p&gt;
&lt;p&gt;Install GNOME tweaks with &lt;code&gt;sudo dnf install -y gnome-tweaks&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Open it, navigate to &lt;code&gt;Windows&lt;/code&gt;, and toggle &lt;code&gt;Maximize&lt;/code&gt; and &lt;code&gt;Minimize&lt;/code&gt; settings.&lt;/p&gt;







  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/min-max.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/min-max_hu_990ebc55af1ace8c.png"
             width="984"
             height="647"
             alt="GNOME tweaks settings as of GNOME 40."
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      GNOME tweaks settings as of GNOME 40.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;h3 id="gnome-app-status-indicators"&gt;GNOME: app status indicators&lt;/h3&gt;
&lt;p&gt;Some apps, such as Solaar, like to run in the background and show you a status indicator on your taskbar.
This functionality is not present on GNOME by default, but you can easily fix it with
the &lt;a href="https://extensions.gnome.org/extension/615/appindicator-support/"&gt;AppIndicator and KStatusNotifierItem Support&lt;/a&gt;
extension.&lt;/p&gt;







  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/appindicator.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/media/appindicator_hu_a8002eb15a753116.png"
             width="525"
             height="262"
             alt="I used to install a handful of extensions in the past to "fix" GNOME, but in 2024 this is the only one I actually need."
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      I used to install a handful of extensions in the past to "fix" GNOME, but in 2024 this is the only one I actually need.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;h3 id="gnome-corner-tiling"&gt;GNOME: corner tiling&lt;/h3&gt;
&lt;p&gt;As of GNOME 46, you cannot do corner tiling out of the box. The best you can do is split windows horizontally, which
is fine for single ultra-wide monitor setups. There are situations where corner tiling comes in really handy.&lt;/p&gt;
&lt;p&gt;The best solution I have found so far seems to
be the &lt;a href="https://extensions.gnome.org/extension/3733/tiling-assistant/"&gt;Tiling Assistant&lt;/a&gt;
extension. It&amp;rsquo;s configurable and will probably suit your workflow well.&lt;/p&gt;
&lt;p&gt;At the time of writing, it does have a few small but annoying enough bugs that are a show-stopper for me, but compared
to other solutions this one at least works.&lt;/p&gt;
&lt;h3 id="optional-disable-selinux"&gt;Optional: disable SELinux&lt;/h3&gt;
&lt;p&gt;There are two types of people: those that swear by &lt;a href="https://github.com/SELinuxProject"&gt;SELinux&lt;/a&gt;, and those that swear
&lt;em&gt;due to&lt;/em&gt; SELinux.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re running into weird issues and you don&amp;rsquo;t know why, then see if SELinux is to blame.&lt;/p&gt;
&lt;p&gt;I run into issues with SELinux every time I try to move files around with &lt;code&gt;rsync&lt;/code&gt;, resulting in a simple
&lt;code&gt;rsync -aAXv source/ destination/&lt;/code&gt; command becoming &lt;code&gt;rsync -aAXv --filter='-x security.selinux' source/ destination/&lt;/code&gt;.
It&amp;rsquo;s frustrating.&lt;/p&gt;
&lt;p&gt;To fix this, I simply disable SELinux. Open up &lt;code&gt;/etc/selinux/config&lt;/code&gt;, find the line &lt;code&gt;SELINUX=enforcing&lt;/code&gt; and change it to
&lt;code&gt;SELINUX=disabled&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll need to restart your machine for it to take effect.&lt;/p&gt;
&lt;h3 id="dont-use-zfs"&gt;Don&amp;rsquo;t use ZFS&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/openzfs/zfs"&gt;ZFS&lt;/a&gt; is a great filesystem, but not on distros that ship new kernel versions.&lt;/p&gt;
&lt;p&gt;This is due to OpenZFS needing to be built against the Linux kernel, and new kernel releases often break compatibility.
Those breakages may lead to your ZFS pools not being imported at boot as a result.&lt;/p&gt;
&lt;p&gt;You &lt;em&gt;can&lt;/em&gt; use ZFS if you use LTS kernels built by someone else, or if you simply don&amp;rsquo;t update your system, but I don&amp;rsquo;t
recommend either approach.&lt;/p&gt;
&lt;p&gt;Use a different distribution, such as Debian, if you really need to use ZFS daily.&lt;/p&gt;
&lt;p&gt;If you don&amp;rsquo;t care for ZFS but like data integrity checks, snapshots and don&amp;rsquo;t use a lot of disks in an array, then
&lt;code&gt;btrfs&lt;/code&gt; &lt;a href="https://ounapuu.ee/posts/2023/10/09/zimaboard/#fedora-server-and-btrfs"&gt;is totally fine.&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="dont-use-nvidia-gpu-s"&gt;Don&amp;rsquo;t use NVIDIA GPU-s&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve never had a good experience with the open source driver &lt;code&gt;nouveau&lt;/code&gt;, and the proprietary drivers are a headache and
break things.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.nvidia.com/blog/nvidia-transitions-fully-towards-open-source-gpu-kernel-modules/"&gt;This might be about to change,&lt;/a&gt;
but I&amp;rsquo;ll believe it when I see it.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I hope that this has made your Fedora Linux experience just a little bit better.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s to hoping that IBM does not fuck up Fedora
Linux. &lt;a href="https://www.redhat.com/en/blog/message-red-hat-associates-today"&gt;My expectations are low.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If you have any other tips to share then please contact me! You can find the contact details below.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;one thing that I appreciate with Fedora is that the kernel versions are ever-so-slightly lagging behind the likes
of Arch Linux, meaning that you&amp;rsquo;ll be less likely to hit any kernel bugs, and yet you don&amp;rsquo;t have to worry about being
too far behind (like on Ubuntu or Debian).&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;no, seriously, I wish you all the luck in the world. I have never managed to have a good experience with NVIDIA on
Linux. I&amp;rsquo;ve completely given up.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;use whatever desktop environment floats your boat, I don&amp;rsquo;t judge!&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>./techtipsy</author><pubDate>Mon, 14 Oct 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/10/14/fedora-starter-pack/</guid></item><item><title>Stream-of-Thought Note Taking</title><link>https://olshansky.info/posts/stream-of-thought-note-taking/</link><description>A tool for humans to put their thoughts on paper</description><author>🦉 olshansky 🦁</author><pubDate>Mon, 14 Oct 2024 04:27:01 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/stream-of-thought-note-taking/</guid></item><item><title>On this day, October 14</title><link>https://stop.zona-m.net/2024/10/on-this-day-october-14/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2009 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Mon, 14 Oct 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/10/on-this-day-october-14/</guid></item><item><title>Gladiator</title><link>https://mehulkar.com/blog/2024/10/gladiator?utm_source=rss</link><description>&lt;div class="letterboxd-movie-data-content"&gt;
   &lt;p&gt;&lt;img src="https://a.ltrbxd.com/resized/film-poster/5/1/9/5/2/51952-gladiator-2000-0-600-0-900-crop.jpg?v=0071a74571" /&gt;&lt;/p&gt; &lt;p&gt;Watched on Monday October 14, 2024.&lt;/p&gt; 
  &lt;p&gt;Rated 4 stars.&lt;/p&gt;&lt;p&gt;
  &lt;/p&gt;&lt;div class="float-clear"&gt;&lt;/div&gt;
&lt;/div&gt;

        &lt;p&gt;Thanks for reading this post via RSS!&lt;/p&gt;</description><author>Mehul Kar's blog</author><pubDate>Mon, 14 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://mehulkar.com/blog/2024/10/gladiator?utm_source=rss</guid></item><item><title>Making Rust builds fail from YAML config mistakes</title><link>https://ntietz.com/blog/rust-complain-yaml-errors/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;I was talking to a friend recently, and zie&lt;sup class="footnote-reference" id="fr-pronoun-1"&gt;&lt;a href="https://ntietz.com/blog/rust-complain-yaml-errors/#fn-pronoun"&gt;[1]&lt;/a&gt;&lt;/sup&gt; lamented that &lt;a href="https://loco.rs/"&gt;a Rust web framework&lt;/a&gt; uses YAML for its configuration.
I'm far from one to defend YAML&lt;sup class="footnote-reference" id="fr-trauma-1"&gt;&lt;a href="https://ntietz.com/blog/rust-complain-yaml-errors/#fn-trauma"&gt;[2]&lt;/a&gt;&lt;/sup&gt;, but dug in a little to understand zir issues with it: is it the trauma here, or is it something else?
Ultimately, zie wanted something that I also seek in Rust: compile time errors over runtime errors.&lt;/p&gt;
&lt;h1 id="checking-it-with-a-test"&gt;Checking it with a test&lt;/h1&gt;
&lt;p&gt;My first thought was to use a test to check the configuration.
This winds up pretty straightforward.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;use &lt;/span&gt;&lt;span&gt;loco_rs::{config::Config, environment::Environment};
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;test&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;can_load_development_config&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;Config::new(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;Environment::Development);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;assert!&lt;/span&gt;&lt;span&gt;(config.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;is_ok&lt;/span&gt;&lt;span&gt;());
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We try to load the config from its default location (&lt;code&gt;./config/development.yaml&lt;/code&gt;), then we check that it did actually load successfully!&lt;/p&gt;
&lt;p&gt;This is a partial solution.
It detects major errors, like malformed YAML files or missing required options.
But it misses the subtle mistakes that can saddle you with a misconfiguration, like misspelling &lt;code&gt;binding&lt;/code&gt; as &lt;code&gt;bindimg&lt;/code&gt;.
Misspelled optional configs are one of the things that can plague a debugging session.
You think you've made a change, but you haven't, and it's often hard to notice a misspelling.&lt;/p&gt;
&lt;p&gt;Can we solve this, too?&lt;/p&gt;
&lt;p&gt;You bet we can.
Kind of.&lt;/p&gt;
&lt;p&gt;The naive idea, which doesn't work well, is to deserialize it into an any-valued type, then round-trip the loaded config as well into one of those, and see if there's anything extra!
This &lt;em&gt;could&lt;/em&gt; work, but you can't do it without a lot of extra effort, since you can't use direct equality.
The one you deserialize, serialize, deserialize again, will have some fields that were &lt;em&gt;added&lt;/em&gt; when you serialized it since they were loaded as default values.&lt;/p&gt;
&lt;p&gt;Instead, we can use &lt;a href="https://github.com/dtolnay/serde-ignored"&gt;serde_ignored&lt;/a&gt; to detect fields which are ignored during deserialization.
We can adapt the example from the crate's README and wind up with this test.
Instead of using the built-in loader we have to read the file in from the disk ourselves and render it (the config file is templated), then deserialize it with our nice &lt;code&gt;serde_ignored&lt;/code&gt; wrapper.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span&gt;#[&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;test&lt;/span&gt;&lt;span&gt;]
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;no_extra_fields_in_development_config&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; filename &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;./config/development.yaml&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; raw_content &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;std::fs::read_to_string(filename).&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;unwrap&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; context &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;Context::new();
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; rendered_content &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;Tera::one_off(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;raw_content, &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;context, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;false&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;unwrap&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; deserializer &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;serde_yaml::Deserializer::from_str(rendered_content.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;as_str&lt;/span&gt;&lt;span&gt;());
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let mut&lt;/span&gt;&lt;span&gt; unused_fields &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;HashSet::new();
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; _config: Config &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;serde_ignored::deserialize(deserializer, |&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;path&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;        unused_fields.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;insert&lt;/span&gt;&lt;span&gt;(path.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;to_string&lt;/span&gt;&lt;span&gt;());
&lt;/span&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;unwrap&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;assert!&lt;/span&gt;&lt;span&gt;(
&lt;/span&gt;&lt;span&gt;        unused_fields.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;is_empty&lt;/span&gt;&lt;span&gt;(),
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;got unexpected fields: {:?}&amp;quot;&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;        unused_fields
&lt;/span&gt;&lt;span&gt;    );
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And then when we run it, we get what we were looking for.&lt;/p&gt;
&lt;pre class="language-text " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-text"&gt;&lt;span&gt;running 2 tests
&lt;/span&gt;&lt;span&gt;test config::tests::can_load_development_config ... ok
&lt;/span&gt;&lt;span&gt;test config::tests::no_extra_fields_in_development_config ... FAILED
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;failures:
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;---- config::tests::no_extra_fields_in_development_config stdout ----
&lt;/span&gt;&lt;span&gt;thread 'config::tests::no_extra_fields_in_development_config' panicked at src/config.rs:31:9:
&lt;/span&gt;&lt;span&gt;got unexpected fields: {&amp;quot;server.bindimg&amp;quot;}
&lt;/span&gt;&lt;span&gt;note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It tells us that we have an unused field and exactly which one it is.
Now we can fix our typo and go on our way!&lt;/p&gt;
&lt;p&gt;This is how I would do it for a real project.
It leaves the config separate from the build so that it can compile even if YAML is messed up, while still giving you guardrails for catching mistakes.
But that's not where the fun ends, because zie specifically wanted a &lt;em&gt;compile-time&lt;/em&gt; error.
Well, Anya, you're in luck.
I gotchu.&lt;/p&gt;
&lt;h1 id="failing-the-build-because-yaml"&gt;Failing the build because YAML&lt;/h1&gt;
&lt;p&gt;Rust lets you hook into the build system by writing a &lt;code&gt;build.rs&lt;/code&gt; file.
It runs before your crate compiles, so you can't really access what's in there.
Usually this is used to compile parts that are written in other languages, or for doing code generation.&lt;/p&gt;
&lt;p&gt;We can certainly abuse it for this, though!&lt;/p&gt;
&lt;p&gt;Let's check if the YAML is malformed first, then worry about detecting unused fields as well.
First, we'll add a few dependencies in the &lt;code&gt;build-dependencies&lt;/code&gt; section of our &lt;code&gt;Cargo.toml&lt;/code&gt; file.
I added &lt;code&gt;loco-rs&lt;/code&gt;, &lt;code&gt;serde&lt;/code&gt;, &lt;code&gt;serde_yaml&lt;/code&gt;, &lt;code&gt;serde_ignored&lt;/code&gt;, and &lt;code&gt;tera&lt;/code&gt;.
We'll only need loco and serde to start, but we'll use the others eventually as well.&lt;/p&gt;
&lt;p&gt;Even though these exist already as dependencies (if we're using Loco), we have to add them as &lt;em&gt;build&lt;/em&gt; dependencies so that they're pulled in early.
This is not a small decision, because it impacts build times, requiring them to be compiled before starting the rest of your build!&lt;/p&gt;
&lt;p&gt;After adding those dependencies, we can write a simple script.
We'll just load the config and, if it fails, we print an error and exit with an error code.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;use &lt;/span&gt;&lt;span&gt;std::process::exit;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;use &lt;/span&gt;&lt;span&gt;loco_rs::{config::Config, environment::Environment};
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;main&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;cargo::rerun-if-changed=config/development.yaml&amp;quot;&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; config &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;Config::new(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;Environment::Development);
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if let &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;Err&lt;/span&gt;&lt;span&gt;(err) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;=&lt;/span&gt;&lt;span&gt; config {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;Error while loading the config: &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, err);
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;exit&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #d3869b;"&gt;1&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now if we run this with malformed configs, we get an error.
Neat!&lt;/p&gt;
&lt;pre class="language-text " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-text"&gt;&lt;span&gt;&amp;gt; cargo build
&lt;/span&gt;&lt;span&gt;   Compiling premove v0.1.0 (/home/nicole/Code/premove-chess)
&lt;/span&gt;&lt;span&gt;error: failed to run custom build command for `premove v0.1.0 (/home/nicole/Code/premove-chess)`
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;Caused by:
&lt;/span&gt;&lt;span&gt;  process didn't exit successfully: `/home/nicole/Code/premove-chess/target/debug/build/premove-f804d605420bf9b9/build-script-build` (exit status: 1)
&lt;/span&gt;&lt;span&gt;  --- stdout
&lt;/span&gt;&lt;span&gt;  cargo::rerun-if-changed=config/development.yaml
&lt;/span&gt;&lt;span&gt;  Error while loading the config: cannot parse `config/development.yaml`: could not find expected ':' at line 168 column 3, while scanning a simple key at line 167 column 1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We have the same problem as before, though, of only getting &lt;em&gt;some&lt;/em&gt; errors.
Let's copy that over.
But this time, we'll be a little nicer, and we'll call unused fields a warning instead of an error&lt;sup class="footnote-reference" id="fr-no-warnings-1"&gt;&lt;a href="https://ntietz.com/blog/rust-complain-yaml-errors/#fn-no-warnings"&gt;[3]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;This looks basically like our test did except that, instead of storing the fields in a set to check for emptiness, we print out a warning each time we hit one.
These are printed in a particular format so that we can tell Cargo they're warnings to pass along.&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;use &lt;/span&gt;&lt;span&gt;loco_rs::config::Config;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;use &lt;/span&gt;&lt;span&gt;tera::{Context, Tera};
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;main&lt;/span&gt;&lt;span&gt;() {
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;cargo::rerun-if-changed=config/development.yaml&amp;quot;&lt;/span&gt;&lt;span&gt;);
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; filename &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;./config/development.yaml&amp;quot;&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; raw_content &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;std::fs::read_to_string(filename).&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;unwrap&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; context &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;Context::new();
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; rendered_content &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;Tera::one_off(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;raw_content, &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span&gt;context, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;false&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;unwrap&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; deserializer &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;serde_yaml::Deserializer::from_str(rendered_content.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;as_str&lt;/span&gt;&lt;span&gt;());
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let&lt;/span&gt;&lt;span&gt; _config: Config &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span&gt;serde_ignored::deserialize(deserializer, |&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;path&lt;/span&gt;&lt;span&gt;| {
&lt;/span&gt;&lt;span&gt;        &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;println!&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;cargo::warning=Unused field in &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;: &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;{}&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, filename, path.&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;to_string&lt;/span&gt;&lt;span&gt;());
&lt;/span&gt;&lt;span&gt;    })
&lt;/span&gt;&lt;span&gt;    .&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;unwrap&lt;/span&gt;&lt;span&gt;();
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we get this nice little warning if we have an unused field!&lt;/p&gt;
&lt;pre class="language-text " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-text"&gt;&lt;span&gt;&amp;gt; cargo build
&lt;/span&gt;&lt;span&gt;   Compiling premove v0.1.0 (/home/nicole/Code/premove-chess)
&lt;/span&gt;&lt;span&gt;warning: premove@0.1.0: Unused field in ./config/development.yaml: server.bindimg
&lt;/span&gt;&lt;span&gt;    Finished `dev` profile [unoptimized + debuginfo] target(s) in 6.47s
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h1 id="don-t-do-this-in-the-build-probably"&gt;Don't do this in the build, probably&lt;/h1&gt;
&lt;p&gt;This is probably a bad idea and you shouldn't do it.
The testing approach is &lt;em&gt;much&lt;/em&gt; better.&lt;/p&gt;
&lt;p&gt;First off, people ignore test failures much less than they ignore warnings.
But if you made unused fields fail the &lt;em&gt;compile step&lt;/em&gt;, then you wouldn't even be able to run any tests at all, which seems like the wrong trade-off to me (since the code itself isn't wrong).&lt;/p&gt;
&lt;p&gt;Then you have the overhead of the build.
If you do this in &lt;code&gt;build.rs&lt;/code&gt;, you end up bringing quite a few dependencies into the &lt;code&gt;build-dependencies&lt;/code&gt; section.
This seems like a bad idea since you're adding a lot of overhead to the upfront stage of compilation, and you also risk these drifting out of sync with the rest of your code.
Cargo will reuse them if it &lt;em&gt;can&lt;/em&gt;, but it can't do that if you end up on different versions in build.rs and elsewhere.&lt;/p&gt;
&lt;p&gt;But perhaps most important is that this is &lt;em&gt;incredibly opaque&lt;/em&gt;.
If you shove important checks into &lt;code&gt;build.rs&lt;/code&gt;, people won't find them as much.
Tests are something we should all be familiar with and using; far fewer of us spelunk into our build systems.
By putting an important check in there, you're hiding it from most people on the project.&lt;/p&gt;
&lt;p&gt;But &lt;em&gt;do&lt;/em&gt; think about putting it in your tests.
It's a nice way to shorten some frustrating debugging sessions.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-pronoun"&gt;
&lt;p&gt;Zie uses zie/zir/zirs pronouns and has a handy pronunciation guide on &lt;a href="https://annahope.me/"&gt;zir homepage&lt;/a&gt;. &lt;a href="https://ntietz.com/blog/rust-complain-yaml-errors/#fr-pronoun-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-trauma"&gt;
&lt;p&gt;I've been traumatized by the piles of YAML that constitute Kubernetes and Helm configurations. &lt;a href="https://ntietz.com/blog/rust-complain-yaml-errors/#fr-trauma-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-no-warnings"&gt;
&lt;p&gt;All warnings should be treated as errors in CI, but it's nice to be able to still, you know, &lt;em&gt;compile things&lt;/em&gt; locally while developing even if you dare leave an unused variable for a moment.
Yes, looking at you, Go. &lt;a href="https://ntietz.com/blog/rust-complain-yaml-errors/#fr-no-warnings-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 14 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/rust-complain-yaml-errors/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Domain transfer code email may take up to 24h to arrive</title><link>https://blog.vslira.net/2024/10/domain-transfer-code-email-may-take-up.html</link><description>Generating this code sounds like it should be quicker. Actually I expected it to be instantaneous, since I had both Porkbun's and Squarespace's tabs open, ready to set both my domains on the former. But it seems like Squarespace will take its sweet time before allowing me not to pay 3x the reasonable rate for a domain maintenance fee.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;I wonder if Domains was that unprofitable for Google to sell it. I hope Blogger is not on the chopping block, but I might migrate just in case.&lt;/div&gt;</description><author>vslira's blog</author><pubDate>Sun, 13 Oct 2024 23:58:57 GMT</pubDate><guid isPermaLink="true">https://blog.vslira.net/2024/10/domain-transfer-code-email-may-take-up.html</guid></item><item><title>Master System Design Interviews: A 6-Step Framework for Success</title><link>https://engineeringatscale.substack.com/p/system-design-interview-success-six-step-framework</link><description>Structured approach to nail your next System Design Interview</description><author>Engineering At Scale</author><pubDate>Sun, 13 Oct 2024 20:28:32 GMT</pubDate><guid isPermaLink="true">https://engineeringatscale.substack.com/p/system-design-interview-success-six-step-framework</guid></item><item><title>Talk: Update on Landlock: IOCTL support</title><link>https://blog.gnoack.org/post/landlock-ioctl-talk</link><description>&lt;p&gt;📢 I gave a talk about the recent changes in Landlock and its new
&lt;a href="https://wiki.gnoack.org/LandlockIoctlControl"&gt;support for restricting IOCTL
usage&lt;/a&gt; at the Linux
Security Summit Europe 2024 in Vienna:&lt;/p&gt;

&lt;p&gt;🌍 &lt;a href="https://sched.co/1ebVW"&gt;Talk page&lt;/a&gt;
| 🎥 &lt;a href="https://www.youtube.com/watch?v=K2onopkMhuM"&gt;Video on YouTube&lt;/a&gt;
| 😎 &lt;a href="https://github.com/landlock-lsm/landlock-logo"&gt;We have stickers!&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="talk-summary"&gt;Talk Summary&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The Landlock security module lets Linux processes restrict what they
can do and puts developers in charge of defining appropriate
sandboxing policies for their programs. We will give a brief
overview over Landlock’s current features, recent developments, and
talk about what is next. We will discuss in more detail Landlock’s
new support for restricting the use of IOCTL and the design
considerations and trade-offs that went into it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="in-other-news"&gt;In other news&lt;/h2&gt;
&lt;p&gt;I finally took the time to finish up the &lt;a href="https://wiki.gnoack.org/LandlockFileSystemCompositionModel"&gt;mathematical writeup of how
Landlock&amp;rsquo;s file system access rights are
composed&lt;/a&gt;
on the wiki.  All of this should be obvious from the documentation,
but it can still be helpful to have a mathematical model to check
against.&lt;/p&gt;</description><author>Blog on blog.gnoack.org</author><pubDate>Sun, 13 Oct 2024 12:15:42 GMT</pubDate><guid isPermaLink="true">https://blog.gnoack.org/post/landlock-ioctl-talk</guid></item><item><title>Silly programs from almost 15 years ago</title><link>andersource.github.io/2026/03/29/andersource.github.io/2024/10/13/silly-programs-15-years.html</link><description>Abusing WinForms' TransparencyKey and TopMost properties</description><author>andersource</author><pubDate>Sun, 13 Oct 2024 11:30:00 GMT</pubDate><guid isPermaLink="true">andersource.github.io/2026/03/29/andersource.github.io/2024/10/13/silly-programs-15-years.html</guid></item><item><title>TinyWasm: How I wrote my own WebAssembly Runtime</title><link>https://blog.henrygressmann.de/2024/tinywasm/</link><description>&lt;p&gt;After a short hiatus from writing on this blog, I'm back with an update on what I've been working on lately (or rather slowly catching up on the backlog of posts I wanted to write).&lt;/p&gt;
&lt;p&gt;I finally finished writing my bachelor's thesis on WebAssembly and Edge Computing this summer. More on that in a later post,
but for now, I wanted to talk about another project I worked on earlier this year that inspired much of the work I did
for my thesis: &lt;a href="https://github.com/explodingcamera/tinywasm"&gt;TinyWasm&lt;/a&gt;, a fully compliant WebAssembly runtime written in Rust.&lt;/p&gt;
&lt;h2 id="tinywasm"&gt;&lt;u&gt;&lt;strong&gt;TinyWasm&lt;/strong&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p&gt;When writing my posts on &lt;a href="https://blog.henrygressmann.de/series/rust-os/"&gt;OS Development&lt;/a&gt; last year, I got interested in WebAssembly
and wanted to try it out inside the kernel. I was fed up with writing context-switching and memory management code,
and WebAssembly looked like an easy way to run existing code in the operating system.
I looked at the existing interpreters and compilers for WebAssembly, but they were all either too complex or had too many dependencies for my taste (I was going for embedded systems, so it had to be lightweight).&lt;/p&gt;
&lt;p&gt;With just a bit of prior experience with WebAssembly and compilers/interpreters, I now had the topic for my capstone project:
Building a WebAssembly runtime. I've been pretty burned on a lot of (unnecessarily) complex projects in the past, so to keep myself on track,
I decided to set out some constraints at the start to finish it on time:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Platform-Specific Code&lt;/strong&gt;: My first goal was to remove all dependencies on platform-specific code so
everything could work in Rust's &lt;code&gt;no_std&lt;/code&gt; environment (and potentially in my OS). This also meant I was limited in the libraries I could use.
Thankfully, an excellent crate for parsing WebAssembly binaries already existed:
&lt;a href="https://github.com/bytecodealliance/wasm-tools"&gt;&lt;code&gt;wasmparser&lt;/code&gt;&lt;/a&gt;. At the time, it didn't support &lt;code&gt;no_std&lt;/code&gt;, but I was able to fork it and make it work.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Build the MVP&lt;/strong&gt;: Focus on the initial version of WebAssembly, so no threads, no SIMD, no garbage collection, etc.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Keep it simple&lt;/strong&gt;: I wanted the codebase to be as small and readable as possible to make it easier to integrate into other projects,
such as my OS. No premature optimization (There has already been a &lt;a href="https://github.com/reef-runtime"&gt;fork of TinyWasm&lt;/a&gt; used as a base for a distributed WebAssembly runtime).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Unsafe Code&lt;/strong&gt;: This came a bit later, but I decided to avoid unsafe Rust code entirely (maybe something for another post). While this excludes some optimizations like using virtual memory to optimize bounds checking in Wasm memory, it also forces me to write simpler code.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I started by taking a simple "Hello World" WebAssembly program and tried to infer everything I needed without looking at the specification.
Surprisingly, this worked well, and in a short time, I had a simple interpreter that could run very basic programs.&lt;/p&gt;

  &lt;figure class="center"&gt;
    
    &lt;a href="https://github.com/explodingcamera/tinywasm/blob/93f8e10a8c15cbcf0d09517869016c32c6bd47eb/crates/tinywasm/src/module/mod.rs#L131-L185" target="_blank"&gt;
    
      &lt;img src="https://blog.henrygressmann.de/2024/tinywasm/assets/code.jpg" /&gt;
    
    &lt;/a&gt;
    

    
      &lt;figcaption class="center"&gt;The first test version of the interpreter.&lt;/figcaption&gt;
    
  &lt;/figure&gt;

&lt;p&gt;With this newly gained confidence, I scrapped the initial codebase and started from scratch.
Starting by defining the structure of the interpreter and the different components it would need, I quickly realized that
I would need a lot of tests to make sure everything worked as expected.
Thankfully, I didn't have to write all of these tests myself, as the reference interpreter conveniently already has
&lt;a href="https://github.com/WebAssembly/testsuite"&gt;thousands of them&lt;/a&gt; covering a lot of edge cases. Plumbing these tests into my test suite was a bit
of a pain, but in the end, I had a script that would run all of the relevant tests and give me a nice graph of
how many tests I had passed (and some dopamine when the number went up).&lt;/p&gt;

  &lt;figure class="center"&gt;
    
    
    
    &lt;svg height="400" viewBox="0 0 1000 400" width="1000" xmlns="http://www.w3.org/2000/svg"&gt;
&lt;text dy="0.76em" fill="#ffffff" font-family="Shantell Sans" font-size="24.193548387096776" font-weight="bold" opacity="1" text-anchor="middle" x="500" y="25"&gt;
WebAssembly 1.0 Test Suite
&lt;/text&gt;
&lt;text dy="0.76em" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" font-weight="bold" opacity="1" text-anchor="middle" transform="rotate(270, 10, 199)" x="10" y="199"&gt;
Tests Passed
&lt;/text&gt;
&lt;text dy="-0.5ex" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" font-weight="bold" opacity="1" text-anchor="middle" x="535" y="390"&gt;
TinyWasm Version
&lt;/text&gt;
&lt;line opacity="0.3" stroke="#ffffff" stroke-width="1" x1="80" x2="989" y1="344" y2="344"&gt;
&lt;line opacity="0.3" stroke="#ffffff" stroke-width="1" x1="80" x2="989" y1="273" y2="273"&gt;
&lt;line opacity="0.3" stroke="#ffffff" stroke-width="1" x1="80" x2="989" y1="201" y2="201"&gt;
&lt;line opacity="0.3" stroke="#ffffff" stroke-width="1" x1="80" x2="989" y1="130" y2="130"&gt;
&lt;line opacity="0.3" stroke="#ffffff" stroke-width="1" x1="80" x2="989" y1="58" y2="58"&gt;
&lt;polyline fill="none" opacity="1" points="79,54 79,344 " stroke="#ffffff" stroke-width="1"&gt;
&lt;text dy="0.5ex" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" opacity="1" text-anchor="end" x="70" y="344"&gt;
0
&lt;/text&gt;
&lt;polyline fill="none" opacity="1" points="74,344 79,344 " stroke="#ffffff" stroke-width="1"&gt;
&lt;text dy="0.5ex" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" opacity="1" text-anchor="end" x="70" y="273"&gt;
5000
&lt;/text&gt;
&lt;polyline fill="none" opacity="1" points="74,273 79,273 " stroke="#ffffff" stroke-width="1"&gt;
&lt;text dy="0.5ex" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" opacity="1" text-anchor="end" x="70" y="201"&gt;
10000
&lt;/text&gt;
&lt;polyline fill="none" opacity="1" points="74,201 79,201 " stroke="#ffffff" stroke-width="1"&gt;
&lt;text dy="0.5ex" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" opacity="1" text-anchor="end" x="70" y="130"&gt;
15000
&lt;/text&gt;
&lt;polyline fill="none" opacity="1" points="74,130 79,130 " stroke="#ffffff" stroke-width="1"&gt;
&lt;text dy="0.5ex" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" opacity="1" text-anchor="end" x="70" y="58"&gt;
20000
&lt;/text&gt;
&lt;polyline fill="none" opacity="1" points="74,58 79,58 " stroke="#ffffff" stroke-width="1"&gt;
&lt;polyline fill="none" opacity="1" points="80,345 989,345 " stroke="#ffffff" stroke-width="1"&gt;
&lt;text dy="0.76em" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" opacity="1" text-anchor="middle" x="170" y="355"&gt;
v0.0.4 (9258)
&lt;/text&gt;
&lt;polyline fill="none" opacity="1" points="170,345 170,350 " stroke="#ffffff" stroke-width="1"&gt;
&lt;text dy="0.76em" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" opacity="1" text-anchor="middle" x="352" y="355"&gt;
v0.0.5 (11135)
&lt;/text&gt;
&lt;polyline fill="none" opacity="1" points="352,345 352,350 " stroke="#ffffff" stroke-width="1"&gt;
&lt;text dy="0.76em" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" opacity="1" text-anchor="middle" x="534" y="355"&gt;
v0.1.0 (17630)
&lt;/text&gt;
&lt;polyline fill="none" opacity="1" points="534,345 534,350 " stroke="#ffffff" stroke-width="1"&gt;
&lt;text dy="0.76em" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" opacity="1" text-anchor="middle" x="716" y="355"&gt;
v0.2.0 (19344)
&lt;/text&gt;
&lt;polyline fill="none" opacity="1" points="716,345 716,350 " stroke="#ffffff" stroke-width="1"&gt;
&lt;text dy="0.76em" fill="#ffffff" font-family="Shantell Sans" font-size="12.096774193548388" opacity="1" text-anchor="middle" x="898" y="355"&gt;
v0.3.0 (20254)
&lt;/text&gt;
&lt;polyline fill="none" opacity="1" points="898,345 898,350 " stroke="#ffffff" stroke-width="1"&gt;
&lt;rect fill="#c8c8ff" height="290" opacity="0.8" stroke="none" width="172" x="812" y="54"&gt;
&lt;rect fill="#3838ff" height="132" opacity="0.8" stroke="none" width="171" x="85" y="212"&gt;
&lt;rect fill="#3838ff" height="159" opacity="0.8" stroke="none" width="172" x="266" y="185"&gt;
&lt;rect fill="#3838ff" height="276" opacity="0.8" stroke="none" width="172" x="630" y="68"&gt;
&lt;rect fill="#3838ff" height="252" opacity="0.8" stroke="none" width="172" x="448" y="92"&gt;
&lt;/svg&gt;

    
    

    
  &lt;/figure&gt;

&lt;p&gt;Now that I had a good test suite, I started implementing the interpreter. The WebAssembly specification is thorough,
but it's also dense with abstract concepts and mathematical notation.
I spent a lot of time looking at different interpreters and their APIs to get a better understanding of how things were supposed
to work (I can recommend the trusty &lt;a href="https://grep.app/"&gt;grep.app&lt;/a&gt; for this).&lt;/p&gt;
&lt;p&gt;For the actual implementation, I mainly started by taking a couple of tests from one of the test suites and trying to get them to pass,
which worked surprisingly well. Slowly but surely, the numbers went up, and more and more tests passed.&lt;/p&gt;
&lt;p&gt;Predictably, once I reached only about 80/2000+ test cases left, I still had about 20% of my work and
a couple of long nights ahead of me. Finally, once all the tests passed, I compiled the interpreter to WebAssembly
and ran it using TinyWasm. It worked on the first try. I was completely surprised, but LLVM randomly did the right optimizations that made it work,
and its code didn't trigger any of the remaining edge cases/bugs.&lt;/p&gt;
&lt;h2 id="optimization"&gt;&lt;u&gt;&lt;strong&gt;Optimization&lt;/strong&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p&gt;Once I had a (mostly) working interpreter, I started looking into profiling and optimizing the code. I had a few ideas on how to make it faster,
but I wanted to optimize only the parts that were slow and not add any additional complexity to the codebase.
I started by profiling the interpreter using &lt;code&gt;perf&lt;/code&gt;, &lt;code&gt;cargo-flamegraph&lt;/code&gt; and later &lt;code&gt;samply&lt;/code&gt; to understand where the bottlenecks were. To keep things going in the right direction,
I also added some basic benchmarks using &lt;code&gt;criterion&lt;/code&gt; to ensure I didn't accidentally make things slower.&lt;/p&gt;

  &lt;figure class="center"&gt;
    
      &lt;img src="https://blog.henrygressmann.de/2024/tinywasm/assets/flamegraph.jpg" /&gt;
    

    
      &lt;figcaption class="center"&gt;A flamegraph using Firefox&amp;#x27;s profiler &amp;amp; samply&lt;/figcaption&gt;
    
  &lt;/figure&gt;

&lt;p&gt;Initially, the biggest overhead was matching opcodes in the interpreter loop. Without using unsafe code,
I had to nudge the compiler in the right direction to generate jump tables for the opcodes. Thankfully, a couple of
&lt;code&gt;#[inline(always)]&lt;/code&gt; annotations and some moving code around did the trick, giving me a nice +50% speedup.
There's not a lot of information on how to do this in Rust, but a &lt;a href="https://pliniker.github.io/post/dispatchers/"&gt;post&lt;/a&gt;
hints at this probably being the easiest cross-platform way to do it.&lt;/p&gt;
&lt;p&gt;From there, I also looked into reducing the size of the bytecode and the interpreter itself. TinyWasm uses a custom bytecode format
that's a bit easier to execute than the standard WebAssembly format and can be zero-copy deserialized (powered by &lt;a href="https://github.com/rkyv/rkyv"&gt;&lt;code&gt;rkyv&lt;/code&gt;&lt;/a&gt;).
This bytecode is represented by a big enum with all the different opcodes and their arguments, and without any optimizations, it was about 32 bytes per instruction.
To reduce this, I removed some redundant information that could be inferred from the context and added more specialized opcodes for common patterns (Super Instructions).
Currently, the bytecode is about 16 bytes per instruction, which is a nice improvement to memory usage and performance due to better memory alignment.&lt;/p&gt;
&lt;p&gt;Currently, The biggest bottleneck is the stack, mainly &lt;code&gt;push&lt;/code&gt; and &lt;code&gt;pop&lt;/code&gt; operations. I'm currently looking into ways to optimize this, but it's tricky without using unsafe code. Other runtimes, such as &lt;a href="https://wasmi-labs.github.io/blog/posts/wasmi-v0.32/"&gt;wasmi&lt;/a&gt;, show that register-based interpreters are much faster. However, I'm not sure if I want to go down that route yet, as it would add a lot of complexity to parsing, and I'd like to stay
as close to the original WebAssembly model as possible.&lt;/p&gt;
&lt;p&gt;For actual performance, I'm currently at about 1/3 of the speed of wasmi, which is pretty good considering the size of the codebase. These benchmarks are not available online yet as this was part of my thesis, but whenever I get around to cleaning them up, I'll publish them on GitHub as well.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;&lt;u&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p&gt;I was super happy with the results, and I'm still pushing the odd update here and there. The next step is SIMD support (currently in the works), for which I recently refactored the stack to use a more efficient representation (SoA for differently sized types). After that, I'll look into adding threads and moving to support the WebAssembly System Interface (WASI). However, I'm waiting for the spec to stabilize before I start implementing it.&lt;/p&gt;
&lt;p&gt;As of now, TinyWasm supports WebAssembly V2 (without SIMD and threads) and several other proposals, such as reference types and bulk memory operations, so most programs should work fine. After submitting it as my capstone project, I also posted it on HN and Reddit, where I got some nice feedback and a few stars on GitHub (obviously the most important part).&lt;/p&gt;

  &lt;figure class="center"&gt;
    
    &lt;a href="https://news.ycombinator.com/item?id=39627410" target="_blank"&gt;
    
      &lt;img src="https://blog.henrygressmann.de/2024/tinywasm/assets/hn.jpg" /&gt;
    
    &lt;/a&gt;
    

    
      &lt;figcaption class="center"&gt;Internet points are important.&lt;/figcaption&gt;
    
  &lt;/figure&gt;

&lt;p&gt;If you're interested in checking it out or maybe even contributing, TinyWasm is up
on &lt;a href="https://github.com/explodingcamera/tinywasm"&gt;GitHub&lt;/a&gt; and also on &lt;a href="https://crates.io/crates/tinywasm"&gt;crates.io&lt;/a&gt;.
Feel free to poke around, open issues, or even submit a PR (I recently improved the test suite and added a small contribution guide).&lt;/p&gt;
&lt;h2 id="further-reading"&gt;&lt;u&gt;&lt;strong&gt;Further Reading&lt;/strong&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://craftinginterpreters.com/"&gt;Crafting Interpreters&lt;/a&gt; by Robert Nystrom is probably the best introduction to the field.
Going from there, I can also recommend the &lt;a href="https://interpreterbook.com/"&gt;Writing an Interpreter in Go&lt;/a&gt;/&lt;a href="https://compilerbook.com/"&gt;Writing a Compiler in Go&lt;/a&gt;
books or the &lt;a href=""&gt;Writing Interpreters in Rust Guide&lt;/a&gt;. I mostly looked at the source code of other interpreters, though, so don't be scared by all the theory. Simple interpreters are surprisingly easy to write, and a small one can be a great weekend project.&lt;/p&gt;</description><author>henry's blog</author><pubDate>Sun, 13 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.henrygressmann.de/2024/tinywasm/</guid></item><item><title>Derby Days</title><link>https://solomon.io/derby-days/</link><description>October is one of is one of my favorite months, and recently we’ve had some perfect porch-sitting weather here in Atlanta.</description><author>Sam Solomon</author><pubDate>Sun, 13 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://solomon.io/derby-days/</guid></item><item><title>String oriented serialization in Rust with Serde</title><link>https://dystroy.org/blog/string-oriented-serialization/</link><description>&lt;p&gt;Serde's derive is fantastic but if you want humans to read and write files, you can't just depend on the standard derive for everything.&lt;/p&gt;
&lt;p&gt;Having your values be written as simple strings is often more convenient.&lt;/p&gt;</description><author>dystroy|Canop / blog</author><pubDate>Sun, 13 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://dystroy.org/blog/string-oriented-serialization/</guid></item><item><title>Town Hall #26: Rounding</title><link>https://taylor.town/town-hall-0026</link><description>just smart enough to be happy</description><author>taylor.town</author><pubDate>Sun, 13 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/town-hall-0026</guid></item><item><title>Maximizing Your Hiking Experience</title><link>https://jasoneckert.github.io/myblog/hiking/</link><description>&lt;p&gt;&lt;img alt="Summit" src="summit.jpg#right" title="Summit" /&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve always loved exploring nature on a good hike, whether it be with other people, my dog, or solo. Hiking builds both physique and mental clarity, and the &amp;ldquo;hiker&amp;rsquo;s high&amp;rdquo; is pure bliss.&lt;/p&gt;
&lt;p&gt;There are many things I&amp;rsquo;ve learned over the years that have helped me maximize my own hiking experience. Some I learned from trial-and-error, while others I learned from fellow hikers, my own physician, or from online hiking forums. This post summarizes all of them using six tips. You can also find a list of my recommended trails &lt;a href="../../trails"&gt;here&lt;/a&gt;.&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Sun, 13 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/hiking/</guid></item><item><title>Too late</title><link>https://blog.erlend.sh/too-late?pk_campaign=rss-feed</link><description/><author>Open Indie</author><pubDate>Sat, 12 Oct 2024 14:33:16 GMT</pubDate><guid isPermaLink="true">https://blog.erlend.sh/too-late?pk_campaign=rss-feed</guid></item><item><title>Interview: Englewood After Dark</title><link>https://www.barbariangrunge.com/p/interview-englewood-after-dark</link><description>An interview with the co-creators of Englewood After Dark, a brand new horror audio drama that just came out this October.</description><author>Barbarian Grunge</author><pubDate>Sat, 12 Oct 2024 13:02:31 GMT</pubDate><guid isPermaLink="true">https://www.barbariangrunge.com/p/interview-englewood-after-dark</guid></item><item><title>Link to https://htmlforpeople.com/</title><link>https://qubyte.codes/links/1728720646687</link><description>&lt;p&gt;Wonderfully written and beautifully presented. This reminds me of my own first efforts on the web using a basic text editor. I&amp;#x27;m going to share this with anyone and everyone who wants to build their own first website. &lt;a href="https://qubyte.codes/links/1728720646687"&gt;HTML for People&lt;/a&gt;&lt;/p&gt;</description><author>Qubyte Codes</author><pubDate>Sat, 12 Oct 2024 11:10:46 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/links/1728720646687</guid></item><item><title>Give people early successes</title><link>https://river.me/blog/teaching-new-devs/</link><description>Someone was wrong on the internet and I&amp;rsquo;ve taught basic coding to a lot of self-described non-developers</description><author>River Writes - A MediaWiki Blog</author><pubDate>Sat, 12 Oct 2024 03:47:26 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/teaching-new-devs/</guid></item><item><title>A History of Microwave Ovens</title><link>https://taylor.town/history-of-microwave-ovens</link><description>Some humans put a ~2.4GHz magnetron on a ventilated metal box with a fancy door.</description><author>taylor.town</author><pubDate>Sat, 12 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/history-of-microwave-ovens</guid></item><item><title>Convert React Javascript project to Typescript - one file at a time</title><link>https://robkohr.com/articles/convert-react-javascript-project-to-typescript---one-file-at-a-time</link><description>Convert React Javascript project to Typescript - one file at a time</description><author>RobKohr's Blog</author><pubDate>Sat, 12 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/convert-react-javascript-project-to-typescript---one-file-at-a-time</guid></item><item><title>In search of the simplest all-in-one blade sharpener</title><link>https://alinpanaitiu.com/blog/making-my-own-sharpening-blocks/</link><description>&lt;p&gt;When I started wood carving, the only sharpening method I remembered was from seeing my mother use some kind of smooth broken stone that she passed over the length of the knife blade before sacrificing a chicken.&lt;/p&gt;
&lt;p&gt;I also remember seeing my father use a very coarse stone wheel placed on a motor shaft which threw many sparks when he sharpened some large axe for splitting wood.&lt;/p&gt;
&lt;p&gt;I had neither of those around anymore in my rented place in the city so I jumped headfirst in the mind numbing and sometimes esoteric art of getting a sharp blade.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/old-sharpening-methods-from-parents.png"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;crude sharpening methods that my parents used&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="crude sharpening methods that my parents used" src="https://alinpanaitiu.comimages/old-sharpening-methods-from-parents.png" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;h2 id="carving-knives"&gt;Carving knives&lt;/h2&gt;
&lt;p&gt;The first blade type I had to sharpen was for my &lt;a href="https://beavercrafttools.com/products/s10-wood-carving-set-of-12-knives" rel="noopener" target="_blank"&gt;BeaverCraft carving knives&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;They fortunately came with a &lt;strong&gt;strop&lt;/strong&gt;, basically a plywood base in the form of a paddle, with leather stuck to it on both sides, and a green waxy bar. Unfortunately I had no idea what to do with it.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/beavercraft-carving-knives.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;BeaverCraft carving knive set&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="BeaverCraft carving knive set" src="https://alinpanaitiu.comimages/beavercraft-carving-knives.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;&lt;em&gt;Stropping&lt;/em&gt; is, at the most basic level, dragging the blade back and forth on a semi-hard surface (like leather). Clean leather won&amp;rsquo;t make your blade sharper though, and that&amp;rsquo;s why you have that green bar which you need to rub onto the leather. It contains a fine abrasive that slowly removes tiny amounts of steel from the very tip of your blade to make it sharper.&lt;/p&gt;
&lt;p&gt;Reading about stropping and watching videos about it gets into a lot of debate on:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;how often to strop&lt;/li&gt;
&lt;li&gt;on what angle should the blade be held on the leather&lt;/li&gt;
&lt;li&gt;should you move from the heel of the blade to the tip, or the reverse&lt;/li&gt;
&lt;li&gt;can you strop too much and round the apex&lt;/li&gt;
&lt;li&gt;what is the green compound for and how should it be applied&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And so on. It’s hard to find much exact science on this, everyone figures it out as they go, and &lt;em&gt;experts&lt;/em&gt; share their beliefs based on their extensive experience.&lt;/p&gt;
&lt;p&gt;I settled on stropping after every 15 minutes of constant carving, but I had no idea what I was doing or if I got any result.&lt;/p&gt;
&lt;p&gt;That’s one thing I learned that would have relieved me of much frustration at the start: &lt;strong&gt;find a way to test the outcome of your motions, otherwise you’ll just keep repeating things that don’t work&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;And it also applies to programming:&lt;/strong&gt; so many times I’ve seen colleagues writing code but with fear and uncertainty as they didn’t know that what they did would have the expected result. And only because they didn’t know how to test how the code would work.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;For example they were backend programmers, that only knew how to run their backend &lt;code&gt;python&lt;/code&gt; server, but had no idea how to run the whole stack to check if the front end did work correctly with their changes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Or app devs who didn’t know how to use a local database, and instead always feared release day because of   how their changes will impact the production db.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Nowadays I know how to test for sharpness.&lt;/p&gt;
&lt;p&gt;If I want a basic working blade I test if the knife cuts printer paper easily and without tearing it. If I want a carving blade, I check if it pops hairs off my arm easily. And I check that often so I don’t do hundreds of blade passes for nothing.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Anyway, back to my carving knives. For a long time I did a pretty bad job at keeping them sharp.&lt;/p&gt;
&lt;p&gt;I would carve for a while then feel the need to push too much into the blade or get tear out in the wood, then I would move the blade back and forth on the strop a few dozens of times, then getting back to carving I would notice a slight improvement which went away after just a few cuts, then back to stropping a thousand times for nothing.&lt;/p&gt;
&lt;p&gt;Stropping is not sharpening, and after so many cuts those blades needed serious sharpening which a piece of leather doesn’t do.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Well, actually, stropping is sharpening, but it’s a very very fine kind of sharpening. It’s similar to using a super fine grit stone, something between 14000 and 100000 grit.&lt;/p&gt;
&lt;p&gt;The green paste is a super fine abrasive that does remove metal from the apex of the blade, but so little at a time that I would need to do thousands of passes to get the same result that a coarse grit sharpening stone would do in a few passes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I started looking into sharpeners, and because &amp;ldquo;stones&amp;rdquo; felt like something old which only my mother used because she didn’t have any better method, I would look into &amp;ldquo;modern&amp;rdquo; sharpeners like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.worksharptools.com/products/pivot-pro-knife-sharpener" rel="noopener" target="_blank"&gt;pull-through V-blades&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.worksharptools.com/products/ceramic-honing-rod" rel="noopener" target="_blank"&gt;honing rods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.worksharptools.com/products/ken-onion-edition-knife-tool-sharpener-mk-2%E2%84%A2" rel="noopener" target="_blank"&gt;powered belt grinders&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.worksharptools.com/products/professional-precision-adjust-knife-sharpener" rel="noopener" target="_blank"&gt;precision sharpening rigs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.worksharptools.com/products/rolling-knife-sharpener" rel="noopener" target="_blank"&gt;rolling sharpeners&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;














&lt;a href="https://alinpanaitiu.comimages/modern-sharpening-methods.png"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;modern sharpening methods I tried&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="modern sharpening methods I tried" src="https://alinpanaitiu.comimages/modern-sharpening-methods.png" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;I tried a few of those expensive methods, ruined a few blades with them, wasted days on reading reviews and watching tutorials&amp;hellip; none of those methods felt like a deterministic way to get a sharp blade every time.&lt;/p&gt;
&lt;p&gt;The precision sharpening rig is probably the closest to that, but I don&amp;rsquo;t want to waste precious workbench space on that. Also, I can&amp;rsquo;t take it with me when traveling.&lt;/p&gt;
&lt;h2 id="wood-planes-and-chisels"&gt;Wood planes and chisels&lt;/h2&gt;
&lt;p&gt;Once I got into larger projects, I discovered how to use wood planes &lt;em&gt;(which seems to be thought of as an antiquated method in Romania, manual woodworking is dead here)&lt;/em&gt;. I also discovered they need frequent sharpening and their blade shape seemed to be perfect for using a flat surface sharpening method like, you know, a stone.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/tiny-block-plane.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;tiny block plane I use for chamfering edges&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="tiny block plane I use for chamfering edges" src="https://alinpanaitiu.comimages/tiny-block-plane.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;Every woodworker on YouTube showed how to sharpen planes on stones so I thought I should probably get one eventually. But which type?&lt;/p&gt;
&lt;p&gt;There are whetstones, natural stones, ceramic stones, oiled stones, diamond stones, you name it.&lt;/p&gt;
&lt;p&gt;After trying a ceramic stone which needed to be soaked in water for 5 minutes before doing any sharpening, and constantly wetted between every few passes, and after making a wet mess and chipping the stone and dreading to sharpen because I had to keep the stone in water, I started looking into drier methods.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Diamond stones&lt;/strong&gt; seemed to be more up my alley: a modern method based on an old, tried and true idea.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Whetstones and oiled stones are great as well, I just wanted something with less maintenance and more resistant to abuse.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;At this point I got sick of wasting money on this hobby, so I got the cheapest set of Chinese diamond plates online. It came in a set of 4 thin steel plates with foam backing, each plate having a different grit: 400, 600, 1000 and 1200. &lt;em&gt;Let’s not get into &lt;a href="https://www.gritomatic.com/pages/grit-fundamentals?srsltid=AfmBOorYfnJneTYSmm5CabLuqF8WgBGAGVSZg441jcCUAHqRQYcBUYSK" rel="noopener" target="_blank"&gt;grit standards&lt;/a&gt;, it’s probably FEPA but who knows.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;And what do you know, it worked, it was easy to use, cheap and no special instructions needed.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/cheap-diamond-plates.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;cheap diamond sharpening plates&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="cheap diamond sharpening plates" src="https://alinpanaitiu.comimages/cheap-diamond-plates.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;I also got lucky in finding out about the &lt;a href="https://youtu.be/pagPuiuA9cY?si=0wNNo5_CaKqrz6Lb" rel="noopener" target="_blank"&gt;OUTDOORS55&lt;/a&gt; YouTube channel and the &lt;a href="https://scienceofsharp.com" rel="noopener" target="_blank"&gt;science of sharp&lt;/a&gt; website who do microscope analysis of blades and methods of sharpening.&lt;/p&gt;
&lt;p&gt;They&amp;rsquo;re getting as close as possible to actually knowing what the heck happens when you move the blade on top of a stone, leather, denim or any type of material people have tried sharpening on.&lt;/p&gt;
&lt;p&gt;This helped me cut through the bullshit fast and get to a simple working method of getting any blade from rusty to shaving sharp in a few minutes.&lt;/p&gt;
&lt;h2 id="my-own-sharpening-blocks"&gt;My own sharpening blocks&lt;/h2&gt;
&lt;p&gt;At this point I had these diamond plates scattered on my workbench, I had a leather strop, a green stropping compound bar that painted my fingers green every time I used it, a round diamond file for my hook knives &lt;em&gt;(because you can’t sharpen the inside of curved blades on a flat stone)&lt;/em&gt; and a piece of flexible leather for stropping those hook knives.&lt;/p&gt;
&lt;p&gt;It worked, but it was a mess. And I also work on the go a lot, I always do some wood project at my parents house, carve some branch at a walk in the woods or need to sharpen both a kitchen knife indoors and a chisel outdoors. I wanted to simplify this, but could not find any ready made product I wanted.&lt;/p&gt;
&lt;p&gt;So I got to doing what I know best, half-assing an improvised product that works for me but would be ashamed of showing it to anyone.&lt;/p&gt;
&lt;h3 id="first-iteration"&gt;First iteration&lt;/h3&gt;
&lt;p&gt;I cut a piece of &lt;code&gt;18mm&lt;/code&gt; thick &lt;a href="https://www.wood-database.com/european-beech/" rel="noopener" target="_blank"&gt;beech wood&lt;/a&gt; into the shape and size of a diamond plate, then stuck a &lt;code&gt;600 grit&lt;/code&gt; plate on one side, and the leather on the other side.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/beech-block-leather-strop.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;beech block with leather strop&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="beech block with leather strop" src="https://alinpanaitiu.comimages/beech-block-leather-strop.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;This not only got the stone and strop higher up from the table which helped a lot with sharpening long knives, but also put them in the same package which I could carry with me.&lt;/p&gt;
&lt;p&gt;I called this a &lt;strong&gt;sharpening block&lt;/strong&gt; in my mind.&lt;/p&gt;
&lt;p&gt;To keep the block from slipping while I moved the blade over it, I cut a piece from a non-slip silicone baking mat. Even the small pressure of the blade makes the silicone adhere well to both the block and whatever surface it&amp;rsquo;s sitting on.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/silicone-nonslip-mat.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;non-slip silicone baking mat piece&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="non-slip silicone baking mat piece" src="https://alinpanaitiu.comimages/silicone-nonslip-mat.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;I still didn’t know what to do if I needed a coarser/finer grit, what to do about the green waxy mess of the stropping compound, and how to carry the hook knife sharpeners.&lt;/p&gt;


  &lt;figure&gt;
    &lt;video controls="controls" loop="loop" style="width: 90%; margin: 10px auto; border-radius: 8px;"&gt;
      &lt;source src="/video/first-sharpening-block-beech.mp4" /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;The first sharpening block, beech wood base, diamond plate, leather strop&lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;h3 id="second-iteration"&gt;Second iteration&lt;/h3&gt;
&lt;p&gt;This summer, my sister-in-law visited from Italy and asked if I could make a coffee table for her. Timing was tight because I had to make it in a week so she could transport it from Romania back to Italy by car.&lt;/p&gt;
&lt;p&gt;I had no time to find wood slabs so I got two &lt;a href="https://www.wood-database.com/red-oak/" rel="noopener" target="_blank"&gt;oak wood&lt;/a&gt; panels from a big-box store, glued them one on top of the other to make the table-top thicker, and figured I&amp;rsquo;ll find some table legs afterwards.&lt;/p&gt;
&lt;p&gt;I found some &lt;code&gt;15cm&lt;/code&gt; diameter smooth beech logs at a firewood seller nearby. Turns out they were leftovers from a veneer factory that can take a really long and thin log slice automatically, leaving the log core as smooth as a turned piece.&lt;/p&gt;


  &lt;figure&gt;
    &lt;video controls="controls" loop="loop" style="width: 90%; margin: 10px auto; border-radius: 8px;"&gt;
      &lt;source src="/video/veneer-log-left-over.mp4" /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;Making veneer out of tree logs&lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;p&gt;The table turned out pretty nice and solid, her children love sitting on top of it or hiding under it with their toys. I guess it could also be used for holding coffee cups, they didn&amp;rsquo;t get a chance to try that yet.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/oak-coffee-table-italy.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;oak coffee table with beech wood legs&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="oak coffee table with beech wood legs" src="https://alinpanaitiu.comimages/oak-coffee-table-italy.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;So this left me with some &lt;code&gt;36mm&lt;/code&gt; thick oak wood that I thought could make for a better sharpening block.&lt;/p&gt;
&lt;p&gt;This time, I glued two coarse and fine grit plates back to back (either &lt;code&gt;400/1000&lt;/code&gt; or &lt;code&gt;600/1200&lt;/code&gt;) and embedded some neodymium magnets inside the wood to keep the diamond steel plates firmly attached but able to flip easily.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/oak-block-magnets.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;oak block with embedded magnets to hold the diamond steel plate&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="oak block with embedded magnets to hold the diamond steel plate" src="https://alinpanaitiu.comimages/oak-block-magnets.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;For the strop, I didn’t have any leather left and it was crazy expensive, so I looked into alternatives.&lt;/p&gt;
&lt;p&gt;You can basically use any semi-hard porous surface for a strop, even plain thick cardboard. It just needs to hold the fine particles of the compound and not flex too much when pressing the blade into it. People seem to use leather, denim, cotton, felt, cardboard, balsa wood etc.&lt;/p&gt;
&lt;p&gt;After building my workbench, and the leg vise for it which needed some &lt;strong&gt;&lt;a href="https://benchcrafted.com/products/crubber" rel="noopener" target="_blank"&gt;rubberized cork&lt;/a&gt;&lt;/strong&gt; for the vise faces, I now found myself with &lt;code&gt;3m²&lt;/code&gt; of that rubberized cork on my hands because I could only buy it in bulk here.&lt;/p&gt;
&lt;p&gt;I tested the cork for stropping and I was amazed to see it’s even better than leather:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;it holds more compound which makes it last more&lt;/li&gt;
&lt;li&gt;the bars of waxy compound break down easier when dragging them across cork, almost like drawing with wax crayons. On leather I needed to heat the bars often.&lt;/li&gt;
&lt;li&gt;it doesn’t get cut as easily. I would often drag the blade wrongly across the leather, cutting into it. The cork is like a self-healing material where cuts disappear&lt;/li&gt;
&lt;li&gt;it’s cheaper&lt;/li&gt;
&lt;li&gt;it can be shaped by sanding, which allows me to give the strop rounded edges that are great for stropping &lt;a href="https://youtu.be/kYT_EE__hX0?si=MJqHOGeuqPsEJJig" rel="noopener" target="_blank"&gt;recurve blades&lt;/a&gt; (those knives which curve to the inside and annoyingly only make contact with the edge of the strop)&lt;/li&gt;
&lt;li&gt;it doesn’t dry and curl like leather&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So I cut and glued a &lt;code&gt;3mm&lt;/code&gt; thick piece of that cork on the other side of the oak base and loaded it with pink corundum stropping compound.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Yes, this is the time I discovered the even more esoteric world of stropping, lapping and honing compounds which are not green bars of waxy stuff. We’ll get to that.&lt;/em&gt;&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/oak-block-cork.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;rubberized cork strop with pink compound&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="rubberized cork strop with pink compound" src="https://alinpanaitiu.comimages/oak-block-cork.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;The &lt;code&gt;36mm&lt;/code&gt; thick oak gave me enough space to drill &lt;code&gt;20mm&lt;/code&gt; diameter holes with a forstner bit, where I could place a wooden dowel lined with that same cork for honing the hook knives. Another hole was for the stropping compound.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/oak-block-compound-hole.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;stropping compound and round strop embedded in the block&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="stropping compound and round strop embedded in the block" src="https://alinpanaitiu.comimages/oak-block-compound-hole.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;I finally reached an all-in-one sharpening block, super stable, with coarse enough grit for getting a bad blade into shape fast, fine grit for sharpening, enough strop for a few years and a way to sharpen recurves and hook knives.&lt;/p&gt;


  &lt;figure&gt;
    &lt;video controls="controls" loop="loop" style="width: 90%; margin: 10px auto; border-radius: 8px;"&gt;
      &lt;source src="/video/thick-oak-sharpening-block.mp4" /&gt;
  &lt;/video&gt;
  &lt;figcaption&gt;Presentation of the oak sharpening block&lt;/figcaption&gt;
  &lt;/figure&gt;

&lt;p&gt;It was a bit too thick though&amp;hellip;&lt;/p&gt;
&lt;h2 id="stropping-compounds"&gt;Stropping compounds&lt;/h2&gt;
&lt;h3 id="the-green-stuff"&gt;The green stuff&lt;/h3&gt;
&lt;p&gt;Most people get a green bar of compound and use it all their life and never think about it. But nooo, I had to do research and see what that compound contains and why is it so waxy and &lt;em&gt;is there better stuff?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My understanding is that the green stuff is made of very fine particles of &lt;strong&gt;chromium oxide&lt;/strong&gt;, which is also used as green pigment in cosmetics and painting, and is why it makes everything in your life green when touched.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/dialux-vert.png"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;Dialux Vert green stropping compound&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="Dialux Vert green stropping compound" src="https://alinpanaitiu.comimages/dialux-vert.png" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;I had a &lt;a href="https://www.osborn.com/en/EUPOSC002~dialux-green" rel="noopener" target="_blank"&gt;Dialux Vert&lt;/a&gt; bar, and searching for its material safety &lt;a href="https://www.thepolishingshop.co.uk/image/catalog/MSDS/Dialux%20green_gb_.pdf" rel="noopener" target="_blank"&gt;data sheet&lt;/a&gt; surfaces the following:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Composition/information on ingredients:&lt;br /&gt;
    mixture of fatty acids and paraffins, aluminum oxide, chromium oxide&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;So it is made of very fine particles of chromium and aluminum oxide suspended in paraffin wax.&lt;/p&gt;
&lt;p&gt;Does that mean it can be melted and poured into cylindrical molds? Yes it does! Adding some green flakes in a small cup with walnut oil in it allows it to be melted in a microwave oven.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Any type of oil works, I tested sunflower oil, olive oil, coconut oil, mineral oil etc. you just need some kind of non-solid fat to make the paste less viscous when melted.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;After lining the &lt;code&gt;20mm&lt;/code&gt; hole with wax paper and pouring the melted mix, it solidified as a green cylinder with a chapstick-like consistency, that I could easily get out and apply to the cork.&lt;/p&gt;
&lt;h3 id="diamonds"&gt;Diamonds&lt;/h3&gt;
&lt;p&gt;In the last decade, diamond pastes and sprays started becoming popular for stropping.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/diamond-stropping-compounds.png"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;diamond powder, paste and spray&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="diamond powder, paste and spray" src="https://alinpanaitiu.comimages/diamond-stropping-compounds.png" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;&lt;a href="https://hitechdiamond.com/products/diamond-powder?variant=33578765811757" rel="noopener" target="_blank"&gt;Diamond powder&lt;/a&gt; was widely used for lapping gemstones for a long time, and &lt;a href="https://hitechdiamond.com/products/diamond-paste?variant=33578750378029" rel="noopener" target="_blank"&gt;diamond paste&lt;/a&gt; was being used in the dental industry for polishing implants. Eventually people figured they can suspend the very fine diamonds in a sprayable emulsion, advertise it for stropping and ask a ton of money for it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.gritomatic.com/products/gunny-juice-poly-diamond-emulsion?variant=40707239149677" rel="noopener" target="_blank"&gt;Diamond sprays&lt;/a&gt; are not accessible here in Romania, but I was able to get some lapping pastes of &lt;code&gt;3 microns&lt;/code&gt; and &lt;code&gt;0.25 microns&lt;/code&gt; particle size to test.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;For reference, chromium oxide green polishing compound is usually formulated with &lt;code&gt;0.5 microns&lt;/code&gt; particle size, but that doesn’t mean that all particles are of that size.&lt;/p&gt;
&lt;p&gt;That’s like the average particle size which gives you an idea of what kind of polish you can expect from it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I admit, I like diamonds. They’re easy to apply by squirting the paste from a syringe, I can throw it in a bag without making a green mess, it does seem to cut metal very slightly faster and lasts a bit longer between applications. But a tiny syringe that lasts 3 months costs more than a green bar that lasts me years. I can’t make this compromise.&lt;/p&gt;
&lt;p&gt;There are people that bought the diamond powder directly and &lt;a href="https://youtu.be/TVJk1K5fm5M?si=BSGkodNpqSN806ai" rel="noopener" target="_blank"&gt;made their own&lt;/a&gt; pastes and emulsions but it all seems too complicated for little gain.&lt;/p&gt;
&lt;p&gt;I did try some cheap &lt;a href="https://www.aliexpress.com/item/1005006308268632.html" rel="noopener" target="_blank"&gt;Chinese AliExpress diamond pastes&lt;/a&gt;, but for the life of me I can’t figure out how they calculate the grit and particle size. I bought the finest I could find which is listed as &lt;code&gt;W0.5 micron mesh&lt;/code&gt;. I don’t know what means but it doesn’t get to a shaving sharp blade easily.&lt;/p&gt;
&lt;h3 id="cubic-boron-nitride"&gt;Cubic boron nitride&lt;/h3&gt;
&lt;p&gt;Looking into other abrasives, there’s another loved stropping compound: cubic boron nitride or &lt;a href="https://www.chefknivestogo.com/ckcbnsp1mi.html" rel="noopener" target="_blank"&gt;CBN emulsions&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;It’s impossible to buy that here, but it’s way too expensive anyway and I’m pretty sure the difference would be marginal.&lt;/p&gt;
&lt;p&gt;People tout that CBN and diamonds are actually &lt;em&gt;necessary&lt;/em&gt; for harder steels, but I&amp;rsquo;ve yet to see that tested. My guess is that it&amp;rsquo;s just people&amp;rsquo;s way of justifying the purchase of a new and expensive toy, we&amp;rsquo;re all guilty of that.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Chromium%28III%29_oxide#Structure_and_properties" rel="noopener" target="_blank"&gt;Wikipedia&lt;/a&gt; says that &lt;code&gt;Cr₂O₃&lt;/code&gt; has a hardness of &lt;code&gt;8 to 8.5 Mohs&lt;/code&gt; which is far higher than plain steel at &lt;code&gt;4 Mohs&lt;/code&gt; and even higher than tungsten at &lt;code&gt;7.5 Mohs&lt;/code&gt;. If it can scratch tungsten, I&amp;rsquo;d say it can hone a steel knife.&lt;/p&gt;
&lt;h3 id="corundum"&gt;Corundum&lt;/h3&gt;
&lt;p&gt;I eventually found a pale pink compound that has the same fine grit as the green one, but without the color problem.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/pink-corundum-brick.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;800g brick of pink corundum stropping compound&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="800g brick of pink corundum stropping compound" src="https://alinpanaitiu.comimages/pink-corundum-brick.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;It’s made from &lt;a href="https://en.wikipedia.org/wiki/Corundum" rel="noopener" target="_blank"&gt;corundum&lt;/a&gt;, which is aluminum oxide without the chromium. It&amp;rsquo;s harder than the green stuff (at &lt;code&gt;9 Mohs&lt;/code&gt;), doesn&amp;rsquo;t leave its color everywhere, and it&amp;rsquo;s dirt cheap. I bought the &lt;a href="https://dtgrinder.com/termek/polishing-paste-maxfin-7-8-lea-light-green-for-ferrous-metals-800-g-masolat/?lang=en" rel="noopener" target="_blank"&gt;Lea Chromax&lt;/a&gt; brand and I’m very happy with it so far. I paid &lt;code&gt;€10&lt;/code&gt; for a huge &lt;code&gt;800g&lt;/code&gt; brick that will last me a lifetime.&lt;/p&gt;
&lt;p&gt;I break small chalk-like pieces from it that I pocket or leave throughout the house to ensure I always hame some on hand. It applies easily to both cork and leather and doesn&amp;rsquo;t flake off or stick to the blade.&lt;/p&gt;
&lt;p&gt;I gifted sharpening blocks along with pieces of stropping compound to many friends and relatives and the pink Chromax block still looks as large as when I bought it.&lt;/p&gt;
&lt;h2 id="third-iteration"&gt;Third iteration&lt;/h2&gt;
&lt;p&gt;I am working on a way to merge the &lt;em&gt;all-in-one&lt;/em&gt; quality of the thick oak block with the &lt;em&gt;pocketability&lt;/em&gt; of the thin beech block.&lt;/p&gt;
&lt;p&gt;I also got my hands on some narrow &lt;code&gt;20mm&lt;/code&gt; width diamond plates of very fine &lt;code&gt;3000grit&lt;/code&gt;. I find them useful for when I need a very sharp blade for doing finishing cuts on a spoon or when planing dense wood. I’d like to integrate this plate somehow in the block.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/diamond-plate-3000.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;fine diamond plate of 3000 grit&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="fine diamond plate of 3000 grit" src="https://alinpanaitiu.comimages/diamond-plate-3000.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;It could maybe be glued to the side of a &lt;code&gt;20mm&lt;/code&gt; thick block of wood, and used hand held instead of on a table.&lt;/p&gt;
&lt;p&gt;I did try that on the beech block I already had, and it seems to work nicely. It&amp;rsquo;s not that hard to sharpen freehand and handheld as long as it&amp;rsquo;s just for doing the last fine honing on an already sharp blade.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/beech-block-3000-diamonds.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;beech block with the 3000 grit diamond plate on the side&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="beech block with the 3000 grit diamond plate on the side" src="https://alinpanaitiu.comimages/beech-block-3000-diamonds.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;The wax paper method for pouring melted compound is also not ideal. I’m thinking that pouring the compound into a chapstick tube would make it easier to use, and be thinner so it can fit inside a hole in the wood block.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;I also have these round diamond files which are great for sharpening round blades, serrated knifes, drill bits, even some rip cut saws. They come as a double-ended rod with a conical file on one end, and a round + flat file on the other end, and they&amp;rsquo;re meant to be placed in a pencil-like holder.&lt;/p&gt;














&lt;a href="https://alinpanaitiu.comimages/diamond-round-files.jpeg"&gt;
  &lt;figure&gt;
    &lt;figcaption&gt;round diamond file, cut in half&lt;/figcaption&gt;
    
      
        
          
          
          
            &lt;source type="image/avif" /&gt;
          
          
            &lt;source type="image/webp" /&gt;
          
          &lt;source /&gt;
        
      
      &lt;img alt="round diamond file, cut in half" src="https://alinpanaitiu.comimages/diamond-round-files.jpeg" /&gt;
    
  &lt;/figure&gt;
&lt;/a&gt;

&lt;p&gt;I cut the rod in half with an angle grinder as I can&amp;rsquo;t fit the whole length of the rod inside the wood block. With only half of it, I can drill a &lt;code&gt;6mm&lt;/code&gt; hole in the block to embed the file.&lt;/p&gt;
&lt;p&gt;I can&amp;rsquo;t fit the cork lined wooden rod for honing though. I only have &lt;code&gt;3mm&lt;/code&gt; thick cork for now which is way too thick for this use case.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s this thing called &lt;a href="https://www.knivesandtools.com/en/pt/-jende-nanocloth-ultra-strop-1-micron-bench-strop.htm" rel="noopener" target="_blank"&gt;Nanocloth&lt;/a&gt; which is like a thin microfiber cloth specifically for CBN and diamond emulsions. It&amp;rsquo;s hella expensive and I would never buy such a thing, but it gave me an idea: I could try wrapping some thin felt cloth around a &lt;code&gt;10mm&lt;/code&gt; diameter wooden dowel. It should hopefully fit in the block and hold enough compound for stropping.&lt;/p&gt;
&lt;p&gt;And I guess that&amp;rsquo;s it, that would be my ideal sharpening method:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a compact block of solid wood&lt;/li&gt;
&lt;li&gt;with 2 wide diamond plates on one face &lt;em&gt;(coarse and fine grit)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;a cork strop on the other face&lt;/li&gt;
&lt;li&gt;a narrow diamond plate on the side &lt;em&gt;(very fine grit)&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;an embedded round diamond file&lt;/li&gt;
&lt;li&gt;an embedded strop rod&lt;/li&gt;
&lt;li&gt;and a stropping compound, inside the wood block somehow, that&amp;rsquo;s easy to get out and apply&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And all that in a cheap package that should cost less than $30 to assemble.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m still working on it, I prepared some new &lt;code&gt;18mm&lt;/code&gt; oak blocks and I&amp;rsquo;ll update this post with the results.&lt;/p&gt;</description><author>Copper • A blog about conductive layers</author><pubDate>Fri, 11 Oct 2024 22:54:06 GMT</pubDate><guid isPermaLink="true">https://alinpanaitiu.com/blog/making-my-own-sharpening-blocks/</guid></item><item><title>Checking the walls</title><link>https://www.extua.pw/blog/2024/10/12/checking_the_walls/</link><description>This morning I went to see Lord Mayor Mike Rowley checking the old city wall.</description><author>extua</author><pubDate>Fri, 11 Oct 2024 19:00:00 GMT</pubDate><guid isPermaLink="true">https://www.extua.pw/blog/2024/10/12/checking_the_walls/</guid></item><item><title>Tesla’s Optimus</title><link>https://iam.mt/teslas-optimus/</link><description>Tesla just demoed the Optimus and I think it's dangerous and now is the time for the Government to regulate the manufacturing of such bots.</description><author>Mohnish Thallavajhula</author><pubDate>Fri, 11 Oct 2024 10:36:58 GMT</pubDate><guid isPermaLink="true">https://iam.mt/teslas-optimus/</guid></item><item><title>Every bug/quirk of the Windows resource compiler (rc.exe), probably</title><link>https://www.ryanliptak.com/blog/every-rc-exe-bug-quirk-probably/</link><description>Fuzz testing decades-old software can turn up some curious behaviors</description><author>ryanliptak.com</author><pubDate>Fri, 11 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.ryanliptak.com/blog/every-rc-exe-bug-quirk-probably/</guid></item><item><title>An experiment in async Rust</title><link>https://ochagavia.nl/blog/an-experiment-in-async-rust/</link><description>Have you come across the fancy async / await syntax of some programming languages? Not everyone has jumped onto the bandwagon1, but many popular languages out there support the feature (e.g. Python, Typescript, C#, and obviously Rust). How does it work under the hood, though? What kind of magic is your software doing when you await an asynchronous function? There&amp;rsquo;s lots to learn about the topic, so what would you say to enlightening ourselves through an unusual experiment?</description><author>Consulting on Adolfo Ochagavía</author><pubDate>Fri, 11 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ochagavia.nl/blog/an-experiment-in-async-rust/</guid></item><item><title>2024-10-11</title><link>https://ho.dges.online/pictures/2024-10-11/</link><description/><author>ho.dges.online</author><pubDate>Fri, 11 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-10-11/</guid></item><item><title>Note 151</title><link>https://qubyte.codes/notes/1728596764525</link><description>&lt;p&gt;It’s not much, but I’ve waited so long to see it. I caught a shooting star while waiting too.&lt;/p&gt;

    &lt;img alt="A photograph of a weak aurora over houses. The pink can be clearly seen." src="https://qubyte.codes/images/1728596702533.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Fri, 11 Oct 2024 00:46:04 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1728596764525</guid></item><item><title>Comparing Prepaid Cell Plans - A Public Spreadsheet (Fi, Cricket, Republic, T-Mobile)</title><link>https://da-data.blogspot.com/2015/10/comparing-prepaid-cell-plans-public.html</link><description>I've been having fun publishing spreadsheets - they give people the ability to stick their own numbers in when they disagree with my assumptions. &amp;nbsp;With the emergence of Google Fi, I wanted to explore for myself the new(ish) breed of prepaid cell phone plans that have become the logical plan choice for, well, just about anyone. &amp;nbsp;I guess that's not true if your employer covers your plan, but for the rest of it, it's pretty much a no-brainer both financially and from having the freedom to change carriers. &amp;nbsp;I figured I'd share the results. &amp;nbsp;(Note: &amp;nbsp;This is not intended to be comprehensive -- the idea is that you can adapt the spreadsheet pretty easily to compare any other providers I left out. &amp;nbsp;That's why it's a public spreadsheet. &amp;nbsp;Just copy it and go.)&lt;br /&gt;
&lt;br /&gt;
(Most recently updated October 2017)&lt;br /&gt;
&lt;br /&gt;
So, below is a little interactive chart that compares four carriers:&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;a href="https://g.co/fi/r/M4F376"&gt;Google Fi&lt;/a&gt;&lt;/b&gt;&amp;nbsp;- Google's "pay us and we will get your phone on the best possible network" plan, currently invitation only. &amp;nbsp;(Disclosure: &amp;nbsp;I'm on sabbatical as a visiting scientist at Google this year, but the opinions expressed here are strictly my own, and are based only on what's on the Fi page. &amp;nbsp;I don't know anything more about it than you do.). &amp;nbsp;Base cost is $20/month&amp;nbsp;+ $10 for data, and you pay only for what you use in 100MB chunks. &amp;nbsp;You do have to pay taxes on top of it. &amp;nbsp;&lt;i&gt;Big limitation&lt;/i&gt;: &amp;nbsp;Only certain, very modern smartphones are supported, such as the Nexus 6. &amp;nbsp;If you use that link, you get $60 credit after your first month.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;a href="https://www.cricketwireless.com/"&gt;Cricket&lt;/a&gt;&lt;/b&gt;&amp;nbsp;- Now owned by AT&amp;amp;T, an MVNO that resells slightly limited AT&amp;amp;T service, prepaid. &amp;nbsp;Plans (if you auto-pay) are $30, 35, and 45/month for 1, 4, and 8GB, and $55/month for unlimited.&amp;nbsp; A note here: &amp;nbsp;If you have more than 3 phones, Cricket becomes an even better deal. &amp;nbsp; A cool feature: &amp;nbsp;Cricket's prices include all taxes and fees. &amp;nbsp;(Disclosure: &amp;nbsp;My wife is using Cricket now. &amp;nbsp;If you go with Cricket,&amp;nbsp;&lt;a href="https://refer.cricketwireless.com/5Veafgr"&gt;consider using my referral link to get $25 &lt;/a&gt;back from them. &amp;nbsp;If you use that link, I also get $25.)&lt;br /&gt;
&lt;br /&gt;
&lt;b style="text-decoration: line-through;"&gt;&lt;a href="https://republicwireless.com/"&gt;Republic&lt;/a&gt;&lt;/b&gt;&lt;strike&gt;&amp;nbsp;- Radically cheap base plans for people who can use WiFi most of the time. &amp;nbsp;But the $15/GB starts to add up if you use a lot of data. &amp;nbsp;&lt;/strike&gt;&lt;i style="text-decoration: line-through;"&gt;Limitations: &amp;nbsp;&lt;/i&gt;&lt;strike&gt;&amp;nbsp;There may be issues with coverage and bandwidth -- I haven't tried it personally. &amp;nbsp;I would tread carefully here unless really trying to optimize the bill for parsimonious data use.&lt;/strike&gt;&amp;nbsp; Updated&amp;nbsp;- I've removed this one from the graph. &amp;nbsp;After thinking about it more, I think it's a different kind of offering (it's not BYOP, and it does wifi offload without optimized hardware). &amp;nbsp;I'm leaving the data in the spreadsheet, so you can go view it on your own.&lt;br /&gt;
&lt;br /&gt;
&lt;b&gt;&lt;a href="http://prepaid-phones.t-mobile.com/prepaid-plans"&gt;T-Mobile Prepaid&lt;/a&gt;&lt;/b&gt;&amp;nbsp;- A big carrier trying to differentiate themselves from the soul-sucking masses of other big carriers by offering reasonable prepaid plans. &amp;nbsp;They're more expensive than the rest until you start using more than 13GB/month, but also offer a bunch of specials for multiple phones and options if you have many data-only devices.&lt;br /&gt;


Take-home: &amp;nbsp;I suspect Google Fi has slightly better coverage than some of the other options. &amp;nbsp;In the low-bandwidth use regime, it looks like the strongest competitor -- if you use under 1.4GB/month, you'll save money, &lt;i&gt;if you have a phone that works with it. &amp;nbsp;&lt;/i&gt;Republic wireless (in the spreadsheet) is cheaper if you buy one one of their phones, but I'm nervous there.&lt;br /&gt;
&lt;br /&gt;
In the middle-bandwidth regime, the various plans on Cricket look like the best options. &amp;nbsp;That's where I've personally chosen to go, mostly because I don't have a Nexus 6 or other device that can do Fi (I use about 1.2GB/month and would probably be better off on Fi, but it's not worth it to upgrade yet. &amp;nbsp;My Nexus 5 is still working well, so I'll probably get a Nexus 5x once it's out when my phone gets old enough that the battery life gets too poor.)&lt;br /&gt;
&lt;br /&gt;
(2017 update -- I now have a Pixel, and I've switched to Fi.&amp;nbsp; I'm very happy with it because of the International roaming.&amp;nbsp; My monthly bill has hovered about $1-2 more per month than it would be on Cricket.)&lt;br /&gt;
&lt;br /&gt;
Surprisingly, T-Mobile reigns supreme for the bandwidth heavy. &amp;nbsp;If you live on the road, tether aggressively, or use your phone as your home data connection, you'll win with their unlimited plan.&lt;br /&gt;
&lt;br /&gt;
A caveat: &amp;nbsp;If you've got kids who use your plans, the above numbers may change. &amp;nbsp;In particular, some plans will charge you for more data use (Fi, Republic) and others will slow you down to 3G speeds once you exceed your cap (Cricket, T-Mobile). &amp;nbsp;There's no right answer here -- for myself, I'd prefer to pay for speed, but if I were paying for a teenager's plan, I'd want the cap. :)&lt;br /&gt;
&lt;br /&gt;
Don't like my underlying numbers or want to see where it all came from? &amp;nbsp;Here's &lt;a href="https://docs.google.com/spreadsheets/d/1Xtty7olQPDWHxLwqIa0YLJAj8JtMgN8E9fqmuNh3Q48/pubhtml"&gt;the spreadsheet as a viewable &lt;/a&gt;web page, and here's &lt;a href="https://docs.google.com/spreadsheets/d/1Xtty7olQPDWHxLwqIa0YLJAj8JtMgN8E9fqmuNh3Q48/edit?usp=sharing"&gt;the underlying Google Docs spreadsheet.&lt;/a&gt;</description><author>Dave's Data</author><pubDate>Fri, 11 Oct 2024 00:10:16 GMT</pubDate><guid isPermaLink="true">https://da-data.blogspot.com/2015/10/comparing-prepaid-cell-plans-public.html</guid></item><item><title>Aurora Borealis in the Fens</title><link>https://sam.hooke.me/trip/2024/10/aurora-borealis-in-the-fens/</link><description>&lt;p&gt;Less than a month after seeing the Aurora Borealis for the first time &lt;a href="https://sam.hooke.me/trip/2024/09/aurora-borealis/"&gt;during a holiday in Iceland&lt;/a&gt;, we saw them from our own back garden in the Fens. This time the lights were more of a blurry glow without any defining shapes, but they were quite bright at times, with a strong red hue easily visible to the naked eye. As best I can tell, the Kp-index was about 7 or 8 out of 9.&lt;/p&gt;</description><author>Sam Hooke</author><pubDate>Thu, 10 Oct 2024 13:55:00 GMT</pubDate><guid isPermaLink="true">https://sam.hooke.me/trip/2024/10/aurora-borealis-in-the-fens/</guid></item><item><title>Understanding Pressure Stall Information in Linux</title><link>https://andrestc.com/post/pressure-stall-information/</link><description>&lt;p&gt;System administrators often use load averages to quickly assess whether a server is overloaded. These values are easily accessible in tools like uptime and top. However, load averages alone do not provide enough detail about which resource—CPU or I/O—may be causing the bottleneck, and they lack granularity in time and cgroup-specific data.&lt;/p&gt;
&lt;p&gt;According to &lt;code&gt;man uptime&lt;/code&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;System load averages is the average number of processes that are either in a runnable or uninterruptable state.  A process in a runnable state  is  either  using  the  CPU or waiting to use the CPU.  A process in uninterruptable state is waiting for some I/O access, eg waiting for disk. The averages are taken over the three time intervals.  Load averages are not normalized for the number of CPUs in a system, so a load average of 1  means  a  single CPU system is loaded all the time while on a 4 CPU system it means it was idle 75% of the time.&lt;/p&gt;</description><author>andrestc.com</author><pubDate>Thu, 10 Oct 2024 00:30:00 GMT</pubDate><guid isPermaLink="true">https://andrestc.com/post/pressure-stall-information/</guid></item><item><title>⏱️ Elixir Basics: Multi-Process Interval Timer</title><link>https://james-carr.org/posts/2024-10-09-elixir-basics-multiprocess-interval-timer/</link><description>A common code sample I like to write when learning a new language is to have multiple threads or processes &amp;ldquo;do something&amp;rdquo; on an interval. For example, publishing to Kafka or RabbitMQ, invoking a REST API, etc., as a way of simulating a multi-process worker system. As a starting point, I like to build these to simply output a string to the console and then build my way up from there.</description><author>James Carr</author><pubDate>Wed, 09 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://james-carr.org/posts/2024-10-09-elixir-basics-multiprocess-interval-timer/</guid></item><item><title>A Georgist's Guerilla Gardening Guide</title><link>https://taylor.town/oh-trespassing</link><description>Every vacant plot could be a home, park, market, garden, farm...</description><author>taylor.town</author><pubDate>Wed, 09 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/oh-trespassing</guid></item><item><title>Dark/Light Switching for the Terminal</title><link>https://fbrs.io/dark-mode-shell/</link><description>&lt;p&gt;I firmly believe that you should use light mode when the ambient lighting is bright. As far as I know, science agrees with that, since dark text on a white background is easier to read than the opposite.&lt;/p&gt;
&lt;p&gt;But after the sun has set and you’ve dimmed all the lights, staring at a bright screen seems wrong and it looks out of place when the rest of your system switches to dark mode.&lt;/p&gt;
&lt;p&gt;For the longest time I’ve therefore wanted to have the automatic dark/light mode switching in my terminal environment, which consists of Alacritty, tmux, Fish and Neovim (tmux doesn’t define any color values of its own, so we can ignore it).&lt;/p&gt;
&lt;p&gt;I finally got around to spending some time on this not so tricky problem and I’d say I’m 85% there. I use my &lt;a href="https://github.com/cideM/yui"&gt;own color theme&lt;/a&gt; which exports config files for, among other things, all three programs listed above. And all config files are available in a dark and a light mode. Meaning, in Neovim I can simply switch to the dark version with &lt;code&gt;:color yui_dark&lt;/code&gt;. Since Neovim has a nice client/server architecture I can also do this remotely:&lt;/p&gt;
&lt;pre class="language-text "&gt;&lt;code class="language-text"&gt;:let g:lightline.colorscheme = &amp;quot;yui_dark&amp;quot;
    \ | colorscheme yui_dark
    \ | call lightline#init()
    \ | call lightline#colorscheme()
    \ | call lightline#update()&amp;lt;CR&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alacritty also has a nice API for changing config values from the shell. You can read more about it under &lt;code&gt;man alacritty-msg&lt;/code&gt; (and &lt;code&gt;man 5 alacritty&lt;/code&gt; for the config values) but the gist is &lt;code&gt;alacritty msg config -w -1 'colors.cursor.background="#CCCCCC"'&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Lastly, for configuring the Fish shell syntax colors you use shell commands anyway, so the exported “config” is a Fish file that I can call whenever I want. The file has commands like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;set fish_color_match eae9e9
set fish_color_selection --background=474355
set fish_color_search_match --background=4f4b5f
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In other words: I now have shell commands for remotely changing Alacritty, Fish and Neovim from light to dark mode and vice versa. The “only” thing left to do is to find a way of running either of the two variants when the system switches themes.&lt;/p&gt;</description><author/><pubDate>Wed, 09 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://fbrs.io/dark-mode-shell/</guid></item><item><title>tweet.new</title><link>https://james.brooks.page/blog/tweet-new</link><description>I bought a .new domain to simplify tweeting—say hello to tweet.new, your shortcut to posting faster on X!</description><author>James Brooks</author><pubDate>Wed, 09 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://james.brooks.page/blog/tweet-new</guid></item><item><title>Software Components</title><link>https://one.mikro2nd.net/pages/software-components/</link><description>While I&amp;rsquo;m not particularly invested in the End-User Computing scene, a thought around that did occur: When some end-user wants to customise (say) their car (house, furniture, clothes,&amp;hellip;) they
a. don&amp;rsquo;t usually start from scratch, and b. don&amp;rsquo;t usually fuck around with the low-level parts. They customise by swapping components, possibly fiddling with the settings/decorations of the components along the way.
Almost never do they fiddle with (say) the crankshaft or valve settings.</description><author>one mikro2nd</author><pubDate>Wed, 09 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://one.mikro2nd.net/pages/software-components/</guid></item><item><title>Who is the real Romanist?</title><link>https://honza.pokorny.ca/2024/10/who-is-the-real-romanist/</link><description>&lt;figure&gt;&lt;img src="https://honza.pokorny.ca/images/trent.jpg" /&gt;
&lt;/figure&gt;

&lt;p&gt;Reformed believers don&amp;rsquo;t like to be accused of being Romanists.  Yet it&amp;rsquo;s a powerful insult: you can shut down any argument if you can prove your opponent is using &lt;em&gt;Papist&lt;/em&gt; logic.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s talk about the text of Scripture.  Modern critical text advocates call those on the Received Text side Romanists.  And the Received Text folks do the same right back.  Who is the real Romanist?&lt;/p&gt;
&lt;h2 id="are-received-text-advocates-romanists"&gt;Are Received Text advocates Romanists?&lt;/h2&gt;
&lt;p&gt;Some on the Textus Receptus side act as though the KJV is the only English bible translation you are allowed to use.  It&amp;rsquo;s pointed out that this is just like the Roman Catholics and their Latin Vulgate.  As the Council of Trent said: use the Vulgate (or KJV) or else.  But thankfully, this is a fringe view and most will argue that any faithful translation based on the right text is suitable.  Use the NKJV or the MEV if you want.  Or a TR translation in your language; English isn&amp;rsquo;t special.&lt;/p&gt;
&lt;p&gt;Another point that is often raised is that their language of &amp;ldquo;providential preservation&amp;rdquo; (WCF 1.8) and &amp;ldquo;tradition&amp;rdquo; is Roman Catholic.  But Reformed theologians are right to point out that Romanists place tradition and the church above Scripture.  The church (cardinals, bishops, etc) determine what Scripture is.  In the Reformed world, Scripture is self-authenticating and the church (all individual believers) simply recognize it.  The Textus Receptus is only traditional in the sense that there has been strong culture of copying and preserving the word of God.&lt;/p&gt;
&lt;h2 id="are-modern-critical-text-advocates-romanists"&gt;Are modern critical text advocates Romanists?&lt;/h2&gt;
&lt;p&gt;Where does your text come from?  For the modern text advocate, this is the Nestle-Aland Greek New Testament produced by the Institute for New Testament Textual Research at the University of Münster, Germany.  This is a secular, academic institution which is responsible for putting together the text underlying our New Testaments (ESV, NASB, NIV, etc).  Their employees are the new bishops and their researchers are the new cardinals.  Once every decade, they come down from their ivory tower and release a new edition of the Nestle-Aland and all modern translations need to get updated.  The new one is coming soon: I wonder what exciting changes are coming to our bibles?&lt;/p&gt;
&lt;p&gt;Those in the modern critical text camp are called Romanists because their text is created by an elite class and imposed on the people.  Just like the Vulgate.&lt;/p&gt;</description><author>Honza Pokorný</author><pubDate>Wed, 09 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://honza.pokorny.ca/2024/10/who-is-the-real-romanist/</guid></item><item><title>Even your cat poem generator requires proper tests</title><link>https://kaszkowiak.org/en/blog/ai-benchmarks/</link><description>Benchmark your AI application. Learn why and how to do that!</description><author>Blog on Maciej Kaszkowiak</author><pubDate>Wed, 09 Oct 2024 00:00:00 GMT</pubDate><guid isPermaLink="true">https://kaszkowiak.org/en/blog/ai-benchmarks/</guid></item><item><title>Thoughts 10 - Tolerance, Mental Health, Having Fun</title><link>https://blog.separateconcerns.com/2024-08-30-thoughts-10.html</link><description>&lt;blockquote&gt;
&lt;p&gt;“Thoughts” are posts about what has been on my mind. Sometimes practical, sometimes not; often just things I read recently. Less thought out than regular posts.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I have not made a “Thoughts” post in 4.5 months so I had a lot of topics in store. I have decided to focus on “soft” topics only this time, so if you are only looking for technical content you can skip this post.&lt;/p&gt;
&lt;section id="Tolerance"&gt;
&lt;h2&gt;Tolerance&lt;/h2&gt;
&lt;p&gt;In 1959, &lt;a href="https://en.wikipedia.org/wiki/Bertrand_Russell"&gt;Bertrand Russell&lt;/a&gt; was &lt;a href="https://www.youtube.com/watch?v=BupO0oOAyBw&amp;amp;t=1600s"&gt;asked on BBC&lt;/a&gt; what would be worth telling later generations.&lt;/p&gt;
&lt;p&gt;His answer was divided into two parts, intellectual and moral. The latter, I think, may be of some use today.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Love is wise, hatred is foolish.&lt;/p&gt;
&lt;p&gt;In this world, which is getting more and more closely interconnected, we have to learn to tolerate each other. We have to learn to put up with the fact that some people say things that we don’t like.&lt;/p&gt;
&lt;p&gt;We can only live together in that way. And if we are to live together and not die together, we must learn a kind of charity and a kind of tolerance which is absolutely vital to the continuation of human life on this planet.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We tend not to do that. We take sides arbitrarily, probably more easily online than offline. We create fractures that make participation in any community more tiring than it should be. &lt;a href="https://continuations.com/post/751642413712523264/i-miss-writing-here"&gt;Albert Wenger puts it like this&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The world is continuing to descend back into tribalism. […] Words have been rendered devoid of meaning and reduced to pledges of allegiance to a tribe. […]&lt;/p&gt;
&lt;p&gt;I don’t belong to any of the tribes nor would I want to. But the effort required to maintain internally consistent and intellectually honest positions in such an environment is daunting. And it often seems futile.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;/section&gt;
&lt;section id="Mental-health"&gt;
&lt;h2&gt;Mental health&lt;/h2&gt;
&lt;p&gt;I generally avoid that topic online, but I will make an exception.&lt;/p&gt;
&lt;p&gt;Brain chemistry is complicated, and mental health is a multi-dimensional spectrum. We vaguely mark regions of that spectrum and give them disorder names; it is a necessarily simplified model. The frontiers of those regions are blurry and differ between cultures and over time. Two decades ago, for instance, behavior considered normal for kids in France would often have resulted in medication for ADHD in the US.&lt;/p&gt;
&lt;p&gt;Today the number of diagnoses and self-diagnoses is clearly exploding. Part of the reason is that influencers, sometimes paid by medical companies, have made neurodivergence “trendy”: people feel like having a disorder makes them belong to a community. It is even worse for kids, with parents “diagnosing” them and managing to find medical professionals to confirm those diagnoses.&lt;/p&gt;
&lt;p&gt;The problem is that studies have shown that believing that you have a mental disorder when you do not can make you exhibit the same symptoms. However they do not come from the same causes, so medication will not work beyond the placebo effect, but it will mess with your brain chemistry.&lt;/p&gt;
&lt;p&gt;A psychiatrist once told me: “If we can avoid treating something like a disease, we should.” I think this is the right approach. We must go back to considering more zones of the spectrum “normal”.&lt;/p&gt;
&lt;p&gt;Note that I am not saying you should not go to therapy! If anything learning &lt;a href="https://en.wikipedia.org/wiki/Cognitive_behavioral_therapy"&gt;CBT&lt;/a&gt; basics could benefit everyone. But we should &lt;strong&gt;normalize&lt;/strong&gt; therapy instead of labeling everyone with a mental disorder.&lt;/p&gt;
&lt;p&gt;Although I have been thinking about this topic for a long time, posting about it here was prompted first by &lt;a href="https://world.hey.com/dhh/the-endangered-state-of-normality-d632a7fe"&gt;a post by DHH&lt;/a&gt;, and more recently by &lt;a href="https://www.youtube.com/watch?v=3a_as1UDDwU"&gt;a video in French&lt;/a&gt; where I learned about the influence of medical companies like Done and Cerebral. I also have &lt;a href="https://www.amazon.com/Crazy-Like-Us-Globalization-American/dp/1416587098"&gt;a book&lt;/a&gt; about how the American approach to mental health impacts the rest of the world on my reading list.&lt;/p&gt;
&lt;/section&gt;
&lt;section id="Have-fun"&gt;
&lt;h2&gt;Have fun&lt;/h2&gt;
&lt;p&gt;DHH (again) &lt;a href="https://world.hey.com/dhh/why-i-retired-from-the-tech-crusades-107a51ea"&gt;explains very well&lt;/a&gt; why I do not use languages like Rust or C++. (*)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;There is no universal set of trade-offs that’ll make something objectively “work best”. Half the programming conundrum lies in connecting to an enduring source of motivation. I wouldn’t be a happy camper if I had to spend my days programming Rust (but I LOVE so many of the tools coming out of that community by people who DO enjoy just that).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I work mostly with Python nowadays, it is not my favorite language but I tolerate it. &lt;a href="https://astral.sh"&gt;Tools written in Rust&lt;/a&gt; are making my life &lt;strong&gt;much&lt;/strong&gt; better, yet I would not pick Rust if &lt;strong&gt;I&lt;/strong&gt; had to write such tools, even if &lt;a href="https://x.com/awesomekling/status/1822241531501162806"&gt;it may be the best tool for that job&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Beginners often ask me what programming language or algorithms they should learn, and I tell them it does not matter at this stage: they should learn what motivates them the most. The older I get, the more I think this remains somewhat true your whole career.&lt;/p&gt;
&lt;p&gt;Programmers &lt;strong&gt;need&lt;/strong&gt; slack and fun projects, or their output degrades real fast. Young people with no kids can compensate for boring work with side projects, but as you get older you must make sure you find some fun at work. Jason Cohen talks about &lt;a href="https://longform.asmartbear.com/procrastinate/"&gt;procrastinating for success&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Finally, sometimes you’ve got to let yourself do fun stuff at the expense of priorities. Most of what you do in a little company isn’t what you enjoy doing; it’s easy to become frustrated and burned-out. So sometimes you need to allow yourself to recover with plain fun. Whatever the cost of putting off duties, burn-out is even more costly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I would add a little nuance to this: it is perfectly possible that, at some point in your life, your work will motivate you so much that you won’t want to do anything else. And that is perfectly fine, you just have to recognize when that is no longer the case.&lt;/p&gt;
&lt;p&gt;Anyway, happy people become successful, not the other way around. There is &lt;a href="https://www.amazon.com/Happiness-Advantage-Positive-Brain-Success/dp/0307591557/"&gt;a book&lt;/a&gt; about this. We are all different, so figure out what &lt;strong&gt;you&lt;/strong&gt; need to be happy now, and go get it.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(*) The reason I do not like C++ or Rust is not because they are efficient, statically-typed programming languages, it is the complexity. I did write a lot of C and I am enthusiastic about Zig, for instance.&lt;/em&gt;&lt;/p&gt;
&lt;/section&gt;</description><author>Separate Concerns</author><pubDate>Tue, 08 Oct 2024 20:15:00 GMT</pubDate><guid isPermaLink="true">https://blog.separateconcerns.com/2024-08-30-thoughts-10.html</guid></item><item><title>Controlling biomedical devices using pneumatic logic</title><link>http://groverlab.org/news/2024/10/08/rocker.html</link><description>&lt;p&gt;&lt;img alt="" src="/assets/rocker.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;Shane Hoang’s work on using pneumatic logic to control biomedical devices (like the laboratory blood shaker/rocker shown here) was &lt;a href="https://link.springer.com/article/10.1007/s10439-024-03628-4"&gt;published in &lt;em&gt;Annals of Biomedical Engineering&lt;/em&gt;&lt;/a&gt;!  Special thanks to our coauthors Mabel Shehada, Konstantinos Karydis, and Philip Brisk.&lt;/p&gt;</description><author>Grover Lab</author><pubDate>Tue, 08 Oct 2024 12:00:00 GMT</pubDate><guid isPermaLink="true">http://groverlab.org/news/2024/10/08/rocker.html</guid></item><item><title>2024-10-08-002</title><link>https://srijan.ch/notes/2024-10-08-002</link><description>Read an interesting set of posts today: https://lethain.com/extract-the-kernel/ and https://lethain.com/executive-translation/ . The basic concept is: ... executives are generally directionally correct but specifically wrong, and it’s your job to understand the overarching direction without getting distracted by the narrow errors in their idea. This resonates well with my experience. I have been …</description><author>Srijan Choudhary, all posts</author><pubDate>Tue, 08 Oct 2024 10:40:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2024-10-08-002</guid></item><item><title>Getting Started with Candlesticks and Python</title><link>https://blog.adnansiddiqi.me/getting-started-with-candlesticks-and-python/</link><description>&lt;p&gt;This post is part of the T4p Series. In this post, we will briefly discuss candlesticks, their basics, types, and how you can use OHLC data to identify them. I&amp;#8217;ll aim to keep it short and to the point. What is a CandleStick Candlesticks are graphical representations of price movements in financial markets, typically showing the opening, closing, high, and low prices for a specific time period. History of CandleSticks Candlestick charting has a rich history that dates back centuries. It originated in Japan during the 18th century, long before the advent of modern financial markets as we know them today. The story begins with Munehisa Homma, a Japanese rice trader from Sakata. In the 1700s, he began recording price movements in the rice markets, developing a method that eventually evolved into candlestick charting. Homma traded in the Dojima Rice Exchange in Osaka, which was essentially the world&amp;#8217;s first futures market. Homma&amp;#8217;s techniques were so successful that he reportedly made a fortune equivalent to billions in today&amp;#8217;s currency. He wrote several books on the subject, including The Fountain of Gold &amp;#8211; The Three Monkey Record of Money. Anatomy of a Candlestick A candlestick consists of two parts: Body: This is the chunky middle part. It shows you the opening and closing prices. A tall body means there was a big price move that day &amp;#8211; lots of action! A short body? Things were pretty calm. Wick: The thin lines extending above and below the body are known as wicks or shadows. The upper wick shows the highest price reached during the time period while the lower wick shows the lowest price during the time period. The length of the wicks tells you how volatile the price movement was. Long wicks suggest a lot of price fluctuation, while short or no wicks indicate that prices remained more stable around the open and close. The length of the wicks tells you how volatile the price movement was. Long wicks suggest a lot of price fluctuation, while short or no wicks indicate that prices remained more stable around the open and close. CandleSticks Types In a broad sense there are two only two types of candles: Bullish Candle: A candlestick where the closing price is higher than the opening price, indicating that buyers pushed the price up. They are usually green in color. Bearish Candle: A candlestick where the closing price is lower than the opening price, showing that sellers drove the price down. They are usually red in color. These candles are created on a two-dimension chart. Identification of Bullish and Bearish Candle Now the programmer in you can&amp;#8217;t control anymore and you want to know how programmatically you can identify a candlestick, as mentioned candlestick is displayed on a chart hence, let&amp;#8217;s create a chart first. I am going to use mplfinance library that is provided by matplotlib library to create financial charts. Install it first: pip install --upgrade mplfinance Below is the code to generate it: import mplfinance as mpf import pandas as pd def plot_candlestick_from_dict(data): df = pd.DataFrame(data) df['Date'] = pd.to_datetime(df['Date']) df.set_index('Date', inplace=True) mpf.plot(df, type='candle', style='charles', title='Candlestick Chart', ylabel='Price') ohlc_data = [ {"Date":"2024-09-01", "Open":100, "High":106, "Low":95, "Close":102}, ] plot_candlestick_from_dict(ohlc_data) Here I am passing a single entry or I say, the data of a single candlestick to plot it on the chart, on the notebook it is displayed below: The candle is bullish because the closing price is greater than the opening price hence green. Now, how&amp;#8217;d you interpret the above candle? pretty simple! assuming this candle is for an hour.  This candle tells about a tough fight between buyers and sellers. Buyers were dominant but sellers were not giving up either, at some point during the course they had almost beaten buyers(the lower wick that touches 96) but buyers got up and fought back, in the end, buyers were winners. Now, let&amp;#8217;s generate a bearish candle, I am passing this data: {"Date":"2024-09-01", "Open":100, "High":101, "Low":93, "Close":95} and it generates a candle like this: What does this candle tell? This candle shows that sellers were dominant during the course, they tried hard to dump their shares and at some point, knocked out buyers(touches 93). The buyers did show some courage but in the end, it was the sellers who won this battle. Alright, so that was the visual part. What if you want to know whether there is a bullish candle or not. Here is a simple function about it: def is_bullish_bearish(candle_data): result = [] for candle in candle_data: open_price = candle['Open'] close_price = candle['Close'] if close_price &amp;#62; open_price: candle_type = 'Bullish' elif close_price &amp;#60; open_price: candle_type = 'Bearish' else: candle_type = 'Neutral' result.append({"Date": candle["Date"], "Candle": candle_type}) return result When you run it it returns the following: [{'Date': '2024-09-01', 'Candle': 'Neutral'}, {'Date': '2024-09-02', 'Candle': 'Bullish'}, {'Date': '2024-09-03', 'Candle': 'Bearish'}] Conclusion In this post, we covered the basics of candlesticks and how to draw and detect them in Python. In the next post, I&amp;#8217;ll discuss candlestick patterns—there are many to explore in the upcoming posts. If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/getting-started-with-candlesticks-and-python/"&gt;Getting Started with Candlesticks and Python&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Tue, 08 Oct 2024 07:16:51 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/getting-started-with-candlesticks-and-python/</guid></item><item><title>2024-10-08-001</title><link>https://srijan.ch/notes/2024-10-08-001</link><description>Tried using X11 on #Linux the last few days due to some issues with Zoom screensharing in Wayland with the latest pipewire, and I already miss #Wayland. Issues I faced with X11: Smooth scrolling broken Apps work noticeably slower Screen tearing This bug in Emacs GTK build: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=67654 (To be fair, this is a GTK-specific issue, not X11 specific) I will go …</description><author>Srijan Choudhary, all posts</author><pubDate>Tue, 08 Oct 2024 06:45:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2024-10-08-001</guid></item><item><title>Generating Diagrams with with AI / LLMs</title><link>https://smcleod.net/2024/10/generating-diagrams-with-with-ai-/-llms/</link><description>Generating diagrams with AI / LLMs</description><author>smcleod.net</author><pubDate>Tue, 08 Oct 2024 04:00:10 GMT</pubDate><guid isPermaLink="true">https://smcleod.net/2024/10/generating-diagrams-with-with-ai-/-llms/</guid></item><item><title>Richard "Pop" Sullivan</title><link>https://solomon.io/richard-pop-sullivan/</link><description>For years I used email drafts as quick ways to gather notes. I’d hit compose, start writing notes and leave them as a draft until I needed them later.</description><author>Sam Solomon</author><pubDate>Tue, 08 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://solomon.io/richard-pop-sullivan/</guid></item><item><title>My First Game with Carimbo, My Homemade Engine, For my Son</title><link>https://nullonerror.org/2024/10/08/my-first-game-with-carimbo/</link><description>https://youtu.be/nVRzCstyspQ</description><author>NULL on error</author><pubDate>Tue, 08 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nullonerror.org/2024/10/08/my-first-game-with-carimbo/</guid></item><item><title>3 Easy Ways To Add Version Flag in Go</title><link>https://jerrynsh.com/3-easy-ways-to-add-version-flag-in-go/</link><description>&lt;p&gt;One of my favorite things about Go is its distribution process using the &lt;a href="https://pkg.go.dev/cmd/go#hdr-Compile_and_install_packages_and_dependencies"&gt;&lt;code&gt;go install&lt;/code&gt; command&lt;/a&gt;. With &lt;code&gt;go install&lt;/code&gt;, I don&amp;#x2019;t have to deal with the trouble of setting up &lt;code&gt;brew&lt;/code&gt;, &lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;pip&lt;/code&gt;, or any other package manager separately like I had to with some languages. It just&lt;/p&gt;</description><author>Jerry Ng</author><pubDate>Tue, 08 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jerrynsh.com/3-easy-ways-to-add-version-flag-in-go/</guid></item><item><title>Intuitions for Distributed Consensus by Phil Eaton</title><link>http://notes.eatonphil.com/2024-10-08-intuitions-for-distributed-consensus.html</link><description>&lt;p&gt;This is an external talk of mine. Click
&lt;a href="https://www.youtube.com/watch?v=-mikZ93wpUQ"&gt;here&lt;/a&gt;
if you are not redirected.&lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 08 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-10-08-intuitions-for-distributed-consensus.html</guid></item><item><title>Running the Intel VTune Profiler on Fedora</title><link>https://www.jviotti.com/2024/10/08/running-the-intel-vtune-profiler-on-fedora.html</link><description>This article explains how to install and configure the Intel VTune Profiler on Fedora to use advanced analysis such as the Memory Access tool</description><author>Juan Cruz Viotti</author><pubDate>Tue, 08 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.jviotti.com/2024/10/08/running-the-intel-vtune-profiler-on-fedora.html</guid></item><item><title>Fixing Nix When Upgrading To macOS Sequoia</title><link>https://www.danielcorin.com/til/nix/upgrading-to-macos-sequoia/</link><description>Fixing Nix When Upgrading To macOS Sequoia</description><author>Thought Eddies</author><pubDate>Mon, 07 Oct 2024 21:22:14 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/nix/upgrading-to-macos-sequoia/</guid></item><item><title>Strategies in the Animal Rights Movement</title><link>https://joshbaldwin.substack.com/p/strategies-animal-rights-movement</link><description>How organizations have an impact.</description><author>Josh Baldwin</author><pubDate>Mon, 07 Oct 2024 14:26:17 GMT</pubDate><guid isPermaLink="true">https://joshbaldwin.substack.com/p/strategies-animal-rights-movement</guid></item><item><title>I Finally Bought My Dream Airplane</title><link>https://miscdotgeek.com/i-finally-bought-my-dream-airplane/</link><description>&lt;p&gt;Aviation has been a love of mine since I was a very little person. Living in Nevada, seeing posters and ads for the Reno Air Races, specifically the Texans, transfixed me. I was hooked. As a teenager, I got an RC glider and dabbled in RC gliders and planes until my early 20&amp;#8217;s. Later in &amp;#8230; &lt;/p&gt;
&lt;p&gt;&lt;a class="more-link btn" href="https://miscdotgeek.com/i-finally-bought-my-dream-airplane/" rel="noopener noreferrer" target="_self"&gt;Continue reading&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://miscdotgeek.com/i-finally-bought-my-dream-airplane/" rel="noopener noreferrer" target="_self"&gt;I Finally Bought My Dream Airplane&lt;/a&gt; appeared first on &lt;a href="https://miscdotgeek.com" rel="noopener noreferrer" target="_self"&gt;MiscDotGeek&lt;/a&gt;.&lt;/p&gt;</description><author>MiscDotGeek</author><pubDate>Mon, 07 Oct 2024 04:52:53 GMT</pubDate><guid isPermaLink="true">https://miscdotgeek.com/i-finally-bought-my-dream-airplane/</guid></item><item><title>Characteristics of the Best UX</title><link>https://gourav.io/blog/ux</link><description>There’s a core human behavior you should never forget:</description><author>Gourav Goyal</author><pubDate>Mon, 07 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gourav.io/blog/ux</guid></item><item><title>Fun with Go Iterators</title><link>https://xnacly.me/posts/2024/fun-with-iterators/</link><description>Go 1.23 added Iterators, so lets build a js like streaming api</description><author>xnacly - blog</author><pubDate>Mon, 07 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2024/fun-with-iterators/</guid></item><item><title>paper thoughts – questionable research practices (QRPs) in machine learning</title><link>http://christopher-beckham.github.io/2024/10/07/qrps-in-ml.html</link><description>my thoughts on the paper "questionable research practices in machine learning" by leech et al</description><author>Christopher Beckham, PhD</author><pubDate>Mon, 07 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://christopher-beckham.github.io/2024/10/07/qrps-in-ml.html</guid></item><item><title>Licensing can be joyful (and legally dubious)</title><link>https://ntietz.com/blog/licensing-joy-gal/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Software licenses are a reflection of our values.
How you choose to license a piece of software says a lot about what you want to achieve with it.
Do you want to reach the maximum amount of users?
Do you want to ensure future versions remain free and open source?
Do you want to preserve your opportunity to make a profit?&lt;/p&gt;
&lt;p&gt;They can also be used to reflect &lt;em&gt;other&lt;/em&gt; values.
For example, there is the infamous &lt;a href="https://www.json.org/license.html"&gt;JSON license&lt;/a&gt; written by Doug Crockford.
It's essentially the &lt;a href="https://en.wikipedia.org/wiki/MIT_License"&gt;MIT license&lt;/a&gt; with this additional clause:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Software shall be used for Good, not Evil.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This has caused quite some consternation.
It is a legally dubious addition, because "Good" and "Evil" are not defined here.
Many people disagree on what these are.
This is really not enforceable, and it's going to make many corporate lawyers wary of using software under this license&lt;sup class="footnote-reference" id="fr-evil-1"&gt;&lt;a href="https://ntietz.com/blog/licensing-joy-gal/#fn-evil"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;I don't think that enforcing this clause was the point.
The point is more signaling values and just &lt;em&gt;having fun with it&lt;/em&gt;.
I don't think anyone seriously believes that this license will be enforceable, or that it will truly curb the amount of evil in the world.
But will it start conversations?&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;There are a lot of other small, playful licenses.
None of these are going to change the world, but they inject a little joy and play into an area of software that is usually serious and somber.&lt;/p&gt;
&lt;p&gt;When I had to pick a license for my &lt;a href="https://hurl.wtf"&gt;exceptional language (Hurl)&lt;/a&gt;, I went down that serious spiral at first.
What license will give the project the best adoption, or help it achieve its goals?
What &lt;em&gt;are&lt;/em&gt; its goals?&lt;/p&gt;
&lt;p&gt;Well, one its goals was definitely to be &lt;em&gt;funny&lt;/em&gt;.
Another was to make sure that people can use the software for educational purposes.
If I make a language as a joke, I do want people to be able to learn from it and do their own related projects!&lt;/p&gt;
&lt;p&gt;This is where we enter one of the sheerly joyous parts of licensing: the ability to apply &lt;em&gt;multiple&lt;/em&gt; licenses to software so that the user can decide which one to use the software under.
You see a lot of Rust projects dual-licensed under Apache and MIT licenses, because the core language is dual-licensed &lt;a href="https://internals.rust-lang.org/t/rationale-of-apache-dual-licensing/8952/3"&gt;for very good reasons&lt;/a&gt;.
We can apply similar rationale to Hurl's license, and we end up with triple-licensing.&lt;/p&gt;
&lt;p&gt;It's currently available under three licenses, each for a separate purpose.
Licensing it under the &lt;a href="https://en.wikipedia.org/wiki/GNU_Affero_General_Public_License"&gt;AGPL&lt;/a&gt; enables users to create derivative works for their own purposes (probably to learn) as long as it remains licensed the same way.
And then we have a commercial license option, which is there so that if you want to commercialize it, I can get a cut of that&lt;sup class="footnote-reference" id="fr-theft-1"&gt;&lt;a href="https://ntietz.com/blog/licensing-joy-gal/#fn-theft"&gt;[2]&lt;/a&gt;&lt;/sup&gt;.
The final option is to license it under the Gay Agenda License, which was created originally for this project.
This option basically requires you to not be a bigot, and then you can use the software under the MIT license terms.
It seems fair to me.&lt;/p&gt;
&lt;p&gt;When I got through &lt;em&gt;that&lt;/em&gt; license slide at &lt;a href="https://sigbovik.org/2024/"&gt;SIGBOVIK 2024&lt;/a&gt;, I knew that the mission was accomplished: &lt;del&gt;bigotry was defeated&lt;/del&gt; the audience laughed.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;The Gay Agenda License is a modified MIT license which requires you do a few things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You must provide attribution (typical MIT manner)&lt;/li&gt;
&lt;li&gt;You have to stand up for LGBTQ rights&lt;/li&gt;
&lt;li&gt;You have to say "be gay, do crime" during use of the software&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Oh, and if you support restricting LGBTQ rights, then you lose that license right away.
No bigots allowed here.
This is all, of course, written in more complete sentences in the license itself.&lt;/p&gt;
&lt;p&gt;The best thing is that you can use this license &lt;em&gt;today&lt;/em&gt;!
There is a website for the Gay Agenda License, the very fitting &lt;a href="https://gal.gay"&gt;gal.gay&lt;/a&gt;&lt;sup class="footnote-reference" id="fr-most-expensive-1"&gt;&lt;a href="https://ntietz.com/blog/licensing-joy-gal/#fn-most-expensive"&gt;[3]&lt;/a&gt;&lt;/sup&gt;.
The website has all the features you'd expect, like showing the license text, using appropriate flags, and copying the text to the clipboard for ease of putting this in your project.&lt;/p&gt;
&lt;h1 id="frequently-anticipated-questions"&gt;Frequently Anticipated Questions&lt;/h1&gt;
&lt;p&gt;Inspired by Hannah's &lt;a href="https://hannahilea.com/blog/driven-developments/#faqs"&gt;brilliant post's FAQ&lt;/a&gt;, here are answers to your questions that you must have by now.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Is this enforceable?&lt;/strong&gt;
We don't really know until it's tested in court, but if that happens, everyone has already lost.
So, who knows, I hope we don't find out!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Isn't it somewhat ambiguous? What defines what is standing up for LGBTQ rights?&lt;/strong&gt;
Ah, yes, good catch.
This is a big problem for this totally serious license.
Definitely a problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can I use it in my project?&lt;/strong&gt;
Yeah!
Let me know if you do so I can add it into a showcase on the website.
But keep in mind, this is a &lt;del&gt;joke&lt;/del&gt; totally serious license, so only use it on &lt;del&gt;silly things&lt;/del&gt; highly serious commercial projects!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How do I get a commercial license of Hurl?&lt;/strong&gt;
This is supposed to be about the Gay Agenda License, not Hurl.
But since you asked, &lt;a href="mailto:me@ntietz.com"&gt;contact me&lt;/a&gt; for pricing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;When exactly do I have to say "be gay, do crime"?&lt;/strong&gt;
To be safe, it's probably best that you mutter it continuously while using all software.
You never know when it's going to be licensed under the Gay Agenda License, so repeat the mantra to ensure compliance.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Thank you to &lt;a href="https://annahope.me/"&gt;Anya&lt;/a&gt; for the feedback on a draft of this post. Thank you to &lt;a href="https://www.veryth.ink/"&gt;Chris&lt;/a&gt; for building the first version of &lt;a href="https://gal.gay"&gt;gal.gay&lt;/a&gt; for me.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-evil"&gt;
&lt;p&gt;Not for nothing, because most of those corporations would probably be using the software for evil.
So, mission accomplished, I guess? &lt;a href="https://ntietz.com/blog/licensing-joy-gal/#fr-evil-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-theft"&gt;
&lt;p&gt;For some reason, no one has contacted  me for this option yet.
I suspect widespread theft of my software, since &lt;em&gt;surely&lt;/em&gt; people want to use Hurl.
They're not using the third option, since we still see rampant transphobia. &lt;a href="https://ntietz.com/blog/licensing-joy-gal/#fr-theft-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-most-expensive"&gt;
&lt;p&gt;This is my most expensive domain yet at $130 for the first year.
I'm hoping that the price doesn't raise dramatically over time, but I'm not optimistic, since it's a three-letter domain.
That said, anything short of extortion will likely be worth keeping for the &lt;em&gt;wonderful&lt;/em&gt; email addresses I get out of this, being a gay gal myself.
It's easier to spell on the phone than this domain is, anyway. &lt;a href="https://ntietz.com/blog/licensing-joy-gal/#fr-most-expensive-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 07 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/licensing-joy-gal/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Can You Get Root With Only a Cigarette Lighter?</title><link>https://www.da.vidbuchanan.co.uk/blog/dram-emfi.html</link><author>David Buchanan's Blog</author><pubDate>Mon, 07 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.da.vidbuchanan.co.uk/blog/dram-emfi.html</guid></item><item><title>There is no such thing as not voting</title><link>https://www.danstroot.com/posts/2016-11-07-there-is-no-such-thing-as-not-voting</link><description>&lt;img alt="post image" src="https://danstroot.imgix.net/assets/blog/img/dfw2.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;If you are bored and disgusted by politics and don't bother to vote, you are in effect increasing the value of someone else's vote.&lt;br /&gt;&lt;br /&gt;This post &lt;a href="https://www.danstroot.com/posts/2016-11-07-there-is-no-such-thing-as-not-voting"&gt;There is no such thing as not voting&lt;/a&gt; first appeared on &lt;a href="https://www.danstroot.com"&gt;Dan Stroot's Blog&lt;/a&gt;</description><author>Dan Stroot</author><pubDate>Mon, 07 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danstroot.com/posts/2016-11-07-there-is-no-such-thing-as-not-voting</guid></item><item><title>Reflecting on 10 Years on Quadra Island</title><link>https://fractaldragon.net/posts/2024-10-07-quadra-ten-years-reflection.html</link><description>&amp;lt;!doctype html&gt;

    
        
        
        
        Reflecting on 10 Years on Quadra Island - Recursive Ramblings
        
    
    
        &lt;header&gt;
            &lt;div class="logo"&gt;
                &lt;a href="../"&gt;Recursive Ramblings&lt;/a&gt;
            &lt;/div&gt;
            &lt;nav&gt;
                &lt;a href="../about.html"&gt;About&lt;/a&gt;
                &lt;a href="../contact.html"&gt;Contact&lt;/a&gt;
                &lt;a href="../archive.html"&gt;Archive&lt;/a&gt;
		&lt;!-- &lt;a href="/tags/"&gt;Tags&lt;/a&gt; --&gt;
		&lt;a href="../now"&gt;Now&lt;/a&gt;
		&lt;a href="../TIL/"&gt;TIL&lt;/a&gt;
		&lt;!--
		    &lt;a href="/atom.xml"&gt;Atom&lt;/a&gt;
                    &lt;a href="/rss.xml"&gt;RSS&lt;/a&gt;
		    --&gt;
            &lt;/nav&gt;
        &lt;/header&gt;

        
          &lt;h1&gt;Reflecting on 10 Years on Quadra Island&lt;/h1&gt;
	  

            &lt;article&gt;
    &lt;section class="header"&gt;
      Posted on 2024-10-07
      
      
      &lt;div class="row tag-block"&gt;
        
        &lt;span&gt; &lt;a href="../tags/Quadra.html"&gt;[Quadra]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/island.html"&gt;[island]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/lifestyle.html"&gt;[lifestyle]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/100DaysToOffload.html"&gt;[100DaysToOffload]&lt;/a&gt;&lt;/span&gt;
        
      &lt;/div&gt;
      
      
      &lt;br /&gt;
      Tim Lavoie
      
    &lt;/section&gt;
    &lt;section&gt;
        &lt;figure&gt;
&lt;img alt="We shall rebuild! (Feb ’24)" src="../images/feb_snow.gif" /&gt;
&lt;figcaption&gt;&lt;em&gt;We shall rebuild! (Feb ’24)&lt;/em&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="review-1010-absolutely-the-right-thing-for-us"&gt;Review: 10/10, absolutely the right thing for us&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;tl;dr: Big life changes are what you make of them&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;We went through the paperwork of renewing our mortgage again the other
day, a sure sign that we’ve again passed some number of orbits around
our star since last time. In this case, it has been slightly over a
decade since we moved here to Quadra Island, BC.&lt;/p&gt;
&lt;p&gt;The process had begun some months prior to the big move, when it had
only been An Idea. Ideas come, ideas go, but occasionally, they arrive
when you have an ability to really make them happen. That worked out
well for us at the time. It couldn’t have much earlier, and it would
likely be harder to do now. For example, real estate prices at each
end were fairly close together, but they’re pendulums swinging at
different rates.&lt;/p&gt;
&lt;p&gt;Once we’d visited a few times to “try out” the place while shopping
around, we were committed. The house in Oakbank was sold, our stuff
packed up and awaiting a destination. We actually arrived without one
quite in hand! Theresa and I arrived in our Honda Civic with a dog and
three cats, and not too much else. Our friend Marnie put us up for
about six weeks while we got our ducks in order. Or rather, the place
lined up, that came with ducks (and chickens).&lt;/p&gt;
&lt;p&gt;Making the move was a leap of faith in other ways as well. We had come
knowing only a tiny handful of people, inserting ourselves into a
community we were only hoping to get to know. Of course, nobody else
knew us from Adam either.&lt;/p&gt;
&lt;p&gt;The past ten years have been a growth period for us, with many new
experiences. The new location was everything we had hoped for of
course. The place is achingly beautiful, with a landscape that lures
visitors from very far away. Speaking with a friend the other day, the
sheer &lt;em&gt;variety&lt;/em&gt; is awesome. How can you be bored, when simply turning
around shows you something different? For contrast, we’d come from a
very, very flat place. My bike commute’s biggest challenges were the
occasional bridge. Getting used to riding on hills took some doing.&lt;/p&gt;
&lt;p&gt;With the rural property, came new responsibilities. Snow doesn’t
happen often, usually something like that pic at the start of this
post. Our (hobby) farm critters need feeding all the time, especially
when conditions are less amenable to them simply grazing for whatever
is tasty. All that feeding, watering, predator defense, and easing of
our animals’ passage when the time comes was all new.&lt;/p&gt;
&lt;p&gt;A different community provides other growth opportunities as well. Our
career work has changed, with new jobs, coworkers, and fellow
volunteers. Many new friends have been made, all people who have come
to welcome us to the community, often where they have been for much
longer than ourselves.&lt;/p&gt;
&lt;p&gt;I have been asked if we regretted the move, and have to answer with a
resounding &lt;strong&gt;no&lt;/strong&gt;. There are sometimes or people, that we miss. Some
we can visit, and others come out to see us from time to
time. Friendships may wax and wane, but that also happens without
moving three provinces over. We have made a niche, and the niche has
welcomed us. We wouldn’t have it any other way.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: This is post #2 for&lt;/em&gt; &lt;a href="https://100daystooffload.com/"&gt;#100DaysToOffload&lt;/a&gt;.&lt;/p&gt;
    &lt;/section&gt;
    
    &lt;section id="isso-thread"&gt;&lt;/section&gt;
&lt;/article&gt;

        

        &lt;footer&gt;
	    &lt;a href="https://notbyai.fyi/"&gt;&lt;img src="../images/written_by_human.png" /&gt;&lt;/a&gt;
            Site generated using the awesome
            &lt;a href="https://jaspervdj.be/hakyll"&gt;Hakyll&lt;/a&gt;
        &lt;/footer&gt;</description><author>Recursive Ramblings</author><pubDate>Mon, 07 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://fractaldragon.net/posts/2024-10-07-quadra-ten-years-reflection.html</guid></item><item><title>Early Stage Company Offsites</title><link>https://www.swyx.io/early-offsites</link><description>&lt;blockquote&gt;
&lt;p&gt;this post was mostly dictated off the top of my head with Wispr AI&lt;/p&gt;
&lt;/blockquote&gt;</description><author>swyx's site RSS Feed</author><pubDate>Mon, 07 Oct 2024 02:38:50 GMT</pubDate><guid isPermaLink="true">https://www.swyx.io/early-offsites</guid></item><item><title>How I Am Setting Up VMs On Hetzner Cloud</title><link>https://www.morling.dev/blog/how-i-am-setting-up-vms-on-hetzner-cloud/</link><description>&lt;div class="toc" id="toc"&gt;
&lt;div id="toctitle"&gt;Table of Contents&lt;/div&gt;
&lt;ul class="sectlevel1"&gt;
&lt;li&gt;&lt;a href="#_creating_instances"&gt;Creating Instances&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_configuring_ssh"&gt;Configuring SSH&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_provisioning_software"&gt;Provisioning Software&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_try_it_out_yourself"&gt;Try It Out Yourself&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Whenever I’ve need a Linux box for some testing or experimentation,
or projects like the &lt;a href="https://www.morling.dev/blog/1brc-results-are-in/"&gt;One Billion Row Challenge&lt;/a&gt; a few months back,
my go-to solution is &lt;a href="https://www.hetzner.com/"&gt;Hetzner Online&lt;/a&gt;, a data center operator here in Europe.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;Their prices for VMs are unbeatable, starting with 3,92 €/month for two shared vCPUs (either x64 or AArch64), four GB of RAM, and 20 TB of network traffic
(these are prices for their German data centers, they vary between regions).
four dedicated cores with 16 GB, e.g. for running a small web server, will cost you 28.55 €/month.
Getting a box with similar specs on AWS would set you back a multiple of that, with the (outbound) network cost being the largest chunk.
So it’s not a big surprise that more and more people realize the advantages of this offering,
most notably Ruby on Rails creator &lt;a href="https://x.com/dhh/"&gt;David Heinemeier Hansson&lt;/a&gt;,
who has been singing the praise for Hetzner’s dedicated servers, but also their VM instances, quite a bit on &lt;a href="https://x.com/search?q=from%3Adhh%20hetzner&amp;amp;src=typed_query&amp;amp;f=live"&gt;Twitter&lt;/a&gt; lately.&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Sun, 06 Oct 2024 21:25:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/how-i-am-setting-up-vms-on-hetzner-cloud/</guid></item><item><title>Designing Real-Time Collaborative Systems: A Deep Dive into Mouse Pointer Tracking</title><link>https://engineeringatscale.substack.com/p/designing-real-time-collaborative</link><description>A decade ago, system design interview questions were broad, like "Design Facebook" or "Design WhatsApp," which were too complex to cover in an hour.</description><author>Engineering At Scale</author><pubDate>Sun, 06 Oct 2024 19:44:03 GMT</pubDate><guid isPermaLink="true">https://engineeringatscale.substack.com/p/designing-real-time-collaborative</guid></item><item><title>Chromium’s influence on Chromium alternatives</title><link>https://seirdy.one/notes/2024/10/06/chromium-influence-on-chromium-alternatives/</link><description>&lt;p&gt;I don’t think most people realize how Firefox and Safari depend on Google for more than “just” revenue from default search engine deals and prototyping new web platform features.&lt;/p&gt;&lt;p&gt;Off the top of my head, Safari and Firefox use the following Chromium libraries: libwebrtc, libbrotli, libvpx, libwebp, some color management libraries, libjxl (&lt;a href="https://github.com/mozilla/standards-positions/pull/1064"&gt;Chromium may eventually contribute a Rust JPEG-XL implementation to Firefox&lt;/a&gt;; it’s a hard image format to implement!), much of Safari’s cryptography (from BoringSSL), Firefox’s 2D renderer (Skia)…the list goes on. Much of Firefox’s security overhaul in recent years (process isolation, site isolation, user namespace sandboxes, effort on building with ControlFlowIntegrity) is directly inspired by Chromium’s architecture.&lt;/p&gt;&lt;p&gt;Interdependence for independent components can be mutually beneficial. For something to be part of Chromium, it needs to build and test with a battery of sanitizers and receive continuous fuzzing. Mozilla and Safari do something similar. All benefit from libraries getting patched to meet each others’ security requirements. Without Google, Mozilla and Apple must assume responsibility to maintain these libraries to a browser-grade standard.&lt;/p&gt;&lt;p&gt;I see many advocates for Chromium alternatives say the Web would be better without Chromium. That may be true, but Chromium alternatives may also be worse.&lt;/p&gt;&lt;p&gt;For completeness: Firefox and Safari’s influence on Chromium in recent years includes the addition of memory-safe languages, partitioned site storage, &lt;a href="https://seirdy.one//posts/2022/06/04/layered-content-blocking/"&gt;declarative content blocking&lt;/a&gt; (from Safari), and a vague multi-year repeatedly-delayed intent to phase out third-party cookies. Chromium would be no better off without other browser projects.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Sun, 06 Oct 2024 18:46:16 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2024/10/06/chromium-influence-on-chromium-alternatives/</guid></item><item><title>Goodbye Sonos, Hello Bluesound</title><link>https://thoughts.greyh.at/posts/goodbye-sonos-hello-bluesound/</link><description>Music has always been an essential part of my life, and for years, my Sonos system satisfied that need, until a disastrous app update forced me to look elsewhere. This is the story of my switch to the Bluesound ecosystem and how it transformed my listening experience.</description><author>Terminal Thoughts</author><pubDate>Sun, 06 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://thoughts.greyh.at/posts/goodbye-sonos-hello-bluesound/</guid></item><item><title>Asheville</title><link>https://ntietz.com/blog/asheville/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Asheville is in crisis right now.
They're without drinking water, faucets run dry, and it's difficult to &lt;em&gt;flush toilets&lt;/em&gt;.
As of yesterday, the hospital has water (via tanker trucks), but 80% of the public water system is still without running water.&lt;/p&gt;
&lt;p&gt;Things are really bad.
Lots of infrastructure has been washed away.
Even when water is back, there has been tremendous damage done that will take a long time to recover from and rebuild.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;Here's &lt;a href="https://www.npr.org/sections/shots-health-news/2024/10/03/nx-s1-5138093/helene-asheville-nc-drinking-water"&gt;the only national news story&lt;/a&gt; my friend from Asheville had seen which covered the water situation specifically.
It's hard for me to understand why this is not covered more broadly.
And my heart aches for those in and around the Asheville area.&lt;/p&gt;
&lt;p&gt;As I'm far away, I can't do a lot to help.
But I can donate money, which my friend said is the &lt;em&gt;only&lt;/em&gt; donation that would help right now if you aren't in the area.
She specifically pointed me to these two ways to donate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.belovedasheville.com/get-involved/"&gt;Beloved Asheville&lt;/a&gt;: a respected community organization in Asheville, this is a great place to send money to help. (If you're closer to that area, it does look like they have specific things they're asking for as well, but this feels like an "if you can help this way, you'd already know" situation.)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mutualaiddisasterrelief.org/donate/"&gt;Mutual Aid Disaster Relief&lt;/a&gt;: there's a local Asheville chapter which is doing work to help. Also an organization to support for broad disaster recovery in general.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I've donated money.
I hope you will, too, for this and for the many other crises that affect us.
Let's help each other.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Sun, 06 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/asheville/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>2024-10-06</title><link>https://ho.dges.online/pictures/2024-10-06/</link><description/><author>ho.dges.online</author><pubDate>Sun, 06 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-10-06/</guid></item><item><title>AI-Generated Podcasts with NotebookLM</title><link>https://maximumeffort.substack.com/p/ai-generated-podcasts-with-notebooklm</link><description>Maximum Effort, Minimum Reward now has a podcast! I mean, I didn't do any work, but Google did! Hear AI make a podcast explaining an article about AI making a podcast! Are you confused? Me too!</description><author>Maximum Effort, Minimum Reward</author><pubDate>Sun, 06 Oct 2024 02:31:41 GMT</pubDate><guid isPermaLink="true">https://maximumeffort.substack.com/p/ai-generated-podcasts-with-notebooklm</guid></item><item><title>A simple telnet based Taboo game in async Python</title><link>https://bytepawn.com/simple-taboo-server-in-python.html</link><description>&lt;p&gt;I built a simple Python-based Taboo game server using &lt;code&gt;asyncio&lt;/code&gt; to provide a customizable multiplayer experience via Telnet. &lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/taboo.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 06 Oct 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/simple-taboo-server-in-python.html</guid></item><item><title>Hiding Images in XMP Metadata</title><link>https://jszym.com/blog/xmp-steganography/</link><description>I was avoiding getting out of bed when I came across a Mastodon post by @Foone that got me thinking. She had expressed a very reasonable thought, characteristic of her posts✪✪Ok, for the most part. You should consider buying her a coffee, her feed is great., to include descriptions in the metadata of image so you wouldn&amp;rsquo;t have to write and rewrite alt texts every time you upload a file.</description><author>Joseph Szymborski has a blog.</author><pubDate>Sat, 05 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jszym.com/blog/xmp-steganography/</guid></item><item><title>Remove Shadows From Screenshots in macOS</title><link>https://nelson.cloud/remove-shadows-from-screenshots-in-macos/?ref=rss</link><description>Run this command to remove shadows from your screenshots in macOS: &lt;code&gt;defaults write com.apple.screencapture &amp;quot;disable-shadow&amp;quot; -bool &amp;quot;true&amp;quot;&lt;/code&gt;</description><author>Nelson Figueroa</author><pubDate>Sat, 05 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/remove-shadows-from-screenshots-in-macos/?ref=rss</guid></item><item><title>Learning Violin Using SoundSlice</title><link>https://robkohr.com/articles/learning-violin-using-soundslice</link><description>Learning Violin Using SoundSlice</description><author>RobKohr's Blog</author><pubDate>Sat, 05 Oct 2024 02:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/learning-violin-using-soundslice</guid></item><item><title>Financial advice from ChatGPT? Let's give it a try!</title><link>https://thomasvilhena.com/2024/10/financial-advice-from-chatgpt</link><description>&lt;p&gt;I recently watched an interesting video titled &lt;a href="https://www.youtube.com/watch?v=ui7kRlJMqjM"&gt;How AI &amp;amp; LLMs are Shaping Financial Advice&lt;/a&gt; by MIT Professor &lt;a href="https://mitsloan.mit.edu/faculty/directory/andrew-w-lo"&gt;Andrew W. Lo&lt;/a&gt;. In it, he discusses how LLMs could be used for tasks like analyzing financial reports and offering sound financial advice. He also touches on the challenges of oversight, trust and fiduciary duty, sharing his thoughts on how these issues might be addressed in the long run.&lt;/p&gt;

&lt;p&gt;Right after that, YouTube suggested a news report from BBC about the Israel conflict in the Middle East, and that gave me an idea for an experiment. You see, we might be on the verge of an escalation in that conflict, and if it happens (I hope it doesn’t), I’m sure it’ll have a significant impact on the financial markets. I’m also sure that I’m not prepared for it, and that (again, if it happens) I’ll think in hindsight that I could have hedged against the real, though uncertain, possibility of escalation.&lt;/p&gt;

&lt;p&gt;So in this experiment, I prepared a simple CoT (Chain-of-Thought) prompt to enlighten me about the possibilities ahead and advise me on what I could do. Let’s take a look at it below.&lt;/p&gt;

&lt;h1 id="the-prompt"&gt;The prompt&lt;/h1&gt;

&lt;div class="chat-message"&gt;

  &lt;p&gt;If a conflict escalation occurs between Israel and Palestine in the Middle East, potentially involving Lebanon and possibly Iran…&lt;/p&gt;

  &lt;p&gt;1) What will happen to the global economy?&lt;/p&gt;

  &lt;p&gt;1.1) How might the global economy react if the conflict escalates further (e.g., involving the U.S.)?&lt;/p&gt;

  &lt;p&gt;2) Which currencies, commodities, bonds, and investments are likely to strengthen in this scenario, and which ones are likely to weaken?&lt;/p&gt;

  &lt;p&gt;3) How can I protect my capital in such a situation?&lt;/p&gt;

  &lt;p&gt;3.1) If the situation deescalates, what would be a balanced asset allocation strategy to hedge against both escalation and deescalation scenarios?&lt;/p&gt;

&lt;/div&gt;

&lt;p&gt;The purpose of this prompt is to first explain the typical impacts of conflict escalation on the global economy. Then, it analyzes the effects on an investor’s portfolio. Finally, it seeks advice on how to balance the portfolio, considering both the chances of escalation and de-escalation (essentially as a hedge).&lt;/p&gt;

&lt;p&gt;I think with a little more effort, we could’ve made an even better prompt. But it’s friday so let’s give this one a shot and see what we get!&lt;/p&gt;

&lt;h1 id="the-reply"&gt;The reply&lt;/h1&gt;

&lt;div class="chat-message"&gt;

  &lt;p&gt;If the conflict between Israel and Palestine escalates, especially involving countries like Lebanon, Iran, and potentially drawing in global powers like the U.S., the global economy could be affected in several ways. Here’s an overview of what could happen, which investments might be impacted, and how you could protect your capital:&lt;/p&gt;

  &lt;h3 id="global-economic-reaction-to-conflict-escalation"&gt;1. Global Economic Reaction to Conflict Escalation&lt;/h3&gt;
  &lt;p&gt;If the conflict escalates into a broader regional war, the global economy could react in several ways:&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Energy Supply Shocks:&lt;/strong&gt; The Middle East is a crucial hub for global oil production. Any conflict that disrupts production or threatens the Strait of Hormuz (through which a significant percentage of the world’s oil passes) could send oil prices soaring. This would lead to inflationary pressures globally, particularly in energy-dependent economies.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Market Volatility:&lt;/strong&gt; Investors might seek safer investments, leading to increased volatility in equity markets. Emerging markets could be especially vulnerable as they often rely on stable energy prices and foreign investment, both of which would be impacted.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Risk-Off Sentiment:&lt;/strong&gt; Global investors tend to shift to safer, low-risk assets in times of uncertainty or geopolitical turmoil, which can affect multiple asset classes.&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;hr /&gt;

  &lt;h3 id="impact-on-currencies-commodities-bonds-and-investments"&gt;2. Impact on Currencies, Commodities, Bonds, and Investments&lt;/h3&gt;

  &lt;p&gt;&lt;strong&gt;Strengthening Assets:&lt;/strong&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Commodities (Oil and Gold):&lt;/strong&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Oil:&lt;/strong&gt; Any conflict in the Middle East typically leads to a rise in oil prices due to potential supply disruptions. Energy stocks might see a boost as oil prices rise.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Gold:&lt;/strong&gt; Gold is traditionally viewed as a safe-haven asset during geopolitical tensions. Its price tends to rise when uncertainty or war escalates.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Currencies (USD and CHF):&lt;/strong&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;USD (U.S. Dollar):&lt;/strong&gt; The U.S. dollar tends to strengthen during global crises due to its status as a reserve currency and safe haven. Investors flock to the dollar in times of geopolitical instability.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;CHF (Swiss Franc):&lt;/strong&gt; Similar to the U.S. dollar, the Swiss franc is considered a stable, safe currency. It often appreciates during global turmoil.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Bonds (U.S. Treasuries, German Bunds):&lt;/strong&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;U.S. Treasuries:&lt;/strong&gt; Investors tend to move into U.S. Treasuries during times of global uncertainty. This will likely lead to lower yields (higher prices) for these bonds.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;German Bunds:&lt;/strong&gt; German bonds, considered some of the safest in Europe, are another place where investors might park capital during geopolitical crises.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;Weakening Assets:&lt;/strong&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Equities (Especially in Emerging Markets):&lt;/strong&gt;
      &lt;ul&gt;
        &lt;li&gt;Stocks, particularly in riskier or emerging markets, are likely to decline as investor sentiment turns risk-averse. Even developed markets might face downturns as investors sell off riskier assets in favor of bonds or safer investments.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Middle Eastern Currencies (Israeli Shekel, Lebanese Pound):&lt;/strong&gt;
      &lt;ul&gt;
        &lt;li&gt;Currencies of countries directly involved in or geographically close to the conflict are likely to weaken. Investors may lose confidence in these currencies due to instability and fear of prolonged conflict.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;hr /&gt;

  &lt;h3 id="protecting-your-capital-during-escalation"&gt;3. Protecting Your Capital During Escalation&lt;/h3&gt;

  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;Increase Allocation to Safe-Haven Assets:&lt;/strong&gt;
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;Gold and Precious Metals:&lt;/strong&gt; Allocate part of your portfolio to gold or other precious metals.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;U.S. Treasuries or Investment-Grade Bonds:&lt;/strong&gt; Move towards high-quality bonds, such as U.S. Treasuries, or investment-grade corporate bonds to reduce risk and provide stability during market volatility.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;Hold More Cash in USD or CHF:&lt;/strong&gt; Having more liquidity in safe-haven currencies can provide flexibility and safety as the situation evolves.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Energy Sector Exposure:&lt;/strong&gt; Increasing exposure to energy stocks or commodities like oil could provide a hedge against rising energy prices during conflict escalation.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;strong&gt;Diversification:&lt;/strong&gt; Ensure that your portfolio is diversified across geographies, sectors, and asset classes to minimize exposure to any one region or sector impacted by the conflict.&lt;/li&gt;
  &lt;/ul&gt;

  &lt;hr /&gt;

  &lt;h3 id="balanced-asset-allocation-strategy-for-escalation-and-de-escalation-scenarios"&gt;4. Balanced Asset Allocation Strategy for Escalation and De-escalation Scenarios&lt;/h3&gt;

  &lt;p&gt;If you want to hedge against both escalation and de-escalation scenarios, you can adopt a balanced asset allocation strategy:&lt;/p&gt;

  &lt;p&gt;&lt;strong&gt;Escalation Hedge:&lt;/strong&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Commodities Exposure:&lt;/strong&gt; Maintain some exposure to commodities like gold and oil. In times of escalation, these would benefit from flight-to-safety dynamics and potential supply disruptions.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Safe-Haven Bonds:&lt;/strong&gt; Allocate to U.S. Treasuries or other low-risk government bonds, as they would strengthen in the event of increased geopolitical risk.&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;De-escalation Hedge:&lt;/strong&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Growth-Oriented Equities:&lt;/strong&gt; Once the situation deescalates, global equity markets may recover, particularly in sectors that are cyclically sensitive like technology, consumer discretionary, or industrials. Maintain some exposure to these equities to capture potential upside.&lt;/p&gt;
    &lt;/li&gt;
    &lt;li&gt;
      &lt;p&gt;&lt;strong&gt;Emerging Markets Exposure:&lt;/strong&gt; If tensions ease, emerging markets that were initially hit hard may rebound strongly, making them an attractive investment for growth in a de-escalated environment.&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;&lt;strong&gt;Overall Strategy:&lt;/strong&gt;&lt;/p&gt;
  &lt;ul&gt;
    &lt;li&gt;&lt;strong&gt;60/40 or 50/30/20 Allocation:&lt;/strong&gt; A traditional balanced allocation could work:
      &lt;ul&gt;
        &lt;li&gt;&lt;strong&gt;60% Equities:&lt;/strong&gt; Diversify across geographies and sectors (some growth-oriented, some defensive).&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;40% Bonds:&lt;/strong&gt; Split between safe-haven government bonds and investment-grade corporate bonds.&lt;/li&gt;
        &lt;li&gt;&lt;strong&gt;20% Commodities &amp;amp; Alternative Investments:&lt;/strong&gt; This portion can include gold, oil, and possibly some real estate or infrastructure investments that are less correlated with traditional assets.&lt;/li&gt;
      &lt;/ul&gt;
    &lt;/li&gt;
  &lt;/ul&gt;

  &lt;p&gt;By maintaining a diversified portfolio that includes both risk-on and risk-off assets, you can protect your capital and remain prepared for both escalation and de-escalation of geopolitical tensions.&lt;/p&gt;

&lt;/div&gt;

&lt;p&gt;There was a mix of stuff a bit obvious and some things that were new to me in the reply. But hey, sometimes the obvious needs to be said, right? Overall, I thought the info was pretty impressive. Then again, I’m not a financial analyst, so I might be falling into the &lt;a href="https://en.wiktionary.org/wiki/Gell-Mann_Amnesia_effect"&gt;Gell-Mann Amnesia effect&lt;/a&gt; without even realizing it.&lt;/p&gt;

&lt;h1 id="closing-thoughts"&gt;Closing thoughts&lt;/h1&gt;

&lt;p&gt;It was a fun, quick experiment, and I learned a thing or two about global dynamics. Even though I won’t be following this advice for practical reasons, the main goal was to share it openly and get feedback from some friends in the financial sector. I also want to let it sit for a while, maybe check back in a year to see if GPT-4’s advice was somewhat valuable or just generic/simplistic fluff.&lt;/p&gt;</description><author>thomas vilhena</author><pubDate>Sat, 05 Oct 2024 00:00:00 GMT</pubDate><guid isPermaLink="true">https://thomasvilhena.com/2024/10/financial-advice-from-chatgpt</guid></item><item><title>Why I Use Swift To Make Generative Art</title><link>https://thecodist.com/why-i-use-swift-to-make-generative-art/</link><description>&lt;p&gt;Now that I am retired from programming for a living, I make generative art (not AI; see my post &lt;a href="https://artasartist.com/what-is-generative-art/?ref=thecodist.com"&gt;What Is Generative Art?&lt;/a&gt;) every day. I belong to a discord community of generative artists, yet I stick out because I am the only person using Swift as my chosen language.&lt;/p&gt;</description><author>The Codist</author><pubDate>Fri, 04 Oct 2024 18:47:41 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/why-i-use-swift-to-make-generative-art/</guid></item><item><title>Gimme gimme gimme</title><link>https://nicolaiarocci.com/gimme-gimme-gimme/</link><description>&lt;p&gt;Why does &lt;a href="https://www.man7.org/linux/man-pages/man1/man.1.html"&gt;man&lt;/a&gt; print &amp;ldquo;gimme gimme gimme&amp;rdquo; at 00:30?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The maintainer of man is a good friend of mine, and one day six years ago I
jokingly said to him that if you invoke man after midnight it should print
&amp;ldquo;gimme gimme gimme&amp;rdquo;, because of the Abba song called &amp;ldquo;Gimme gimme gimme a man
after midnight&amp;rdquo;.  Well, he did actually put it in. A few people were amused to
discover it, and we mostly forgot about it until today.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Fri, 04 Oct 2024 12:46:13 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/gimme-gimme-gimme/</guid></item><item><title>User Delegation SAS Tokens In Azure Explained</title><link>https://nestenius.se/azure/user-delegation-sas-tokens-in-azure-explained/</link><description>&lt;p&gt;I discovered many interesting Azure features while studying for the AZ-204 certification. One of these features is User Delegation SAS tokens, a way to provide access to Azure Blob Storage. In this blog post, I&amp;#8217;ll share what I learned about these tokens and how to use them in C#/.NET. Accessing Azure Storage Accounts Azure offers [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/azure/user-delegation-sas-tokens-in-azure-explained/"&gt;User Delegation SAS Tokens In Azure Explained&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Fri, 04 Oct 2024 10:14:58 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/azure/user-delegation-sas-tokens-in-azure-explained/</guid></item><item><title>AI Strategy in 2024 for the Rest of Us</title><link>https://bytepawn.com/ai-strategy-in-2024.html</link><description>&lt;p&gt;My review of the AI landscape in late 2024, and what the road ahead looks like for those of us not building foundational models. &lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/ai-strategy-2024.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 04 Oct 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/ai-strategy-in-2024.html</guid></item><item><title>Effective Playtesting Rituals</title><link>https://dustinfreeman.org/blog/playtest-rituals/</link><description>Every community of creators should have a regular playtest series. This is true whether it&amp;#8217;s an ad-hoc community of independent creators, an academic research group, or a large corporation. If there isn&amp;#8217;t a playtest series, you should start one. This post is for you. I have been involved in several recurring playtest series. I first [&amp;#8230;]</description><author>Dustin Freeman</author><pubDate>Fri, 04 Oct 2024 00:46:41 GMT</pubDate><guid isPermaLink="true">https://dustinfreeman.org/blog/playtest-rituals/</guid></item><item><title>Fear and Healing on 5-MeO-DMT</title><link>https://superbowl.substack.com/p/fear-and-healing-on-5-meo-dmt</link><description>Or: how I learned to stop worrying</description><author>Superb Owl</author><pubDate>Thu, 03 Oct 2024 18:22:28 GMT</pubDate><guid isPermaLink="true">https://superbowl.substack.com/p/fear-and-healing-on-5-meo-dmt</guid></item><item><title>20 years blogging</title><link>https://paul.kinlan.me/20-years-blogging/</link><description>Wow! Just realized I've been blogging for over 20 years, starting way back in August 2004 on kinlan.co.uk with Blogger.  The journey has taken me through Posterous and landed me here on paul.kinlan.me with Hugo (and maybe Jekyll at some point).  Sure, there's some cringe-worthy stuff in the archives, but it's &lt;em&gt;my&lt;/em&gt; history.  And honestly, I wouldn't be where I am today without this little corner of the internet.  Huge thanks to Tim Berners-Lee and everyone who's made the web what it is!</description><author>Modern Web Development with Chrome</author><pubDate>Thu, 03 Oct 2024 13:37:00 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/20-years-blogging/</guid></item><item><title>Avoiding the Lambda Doom Loop</title><link>https://aaronstuyvenberg.com/posts/lambda-timeout-doom-loop</link><description>Heads up serverless developers! A recent change in the Lambda sandbox environment changes how timeouts are handled, potentially causing your function to enter a permanent doom loop. This post will explain the change, how to spot it, and how to avoid the doom loop.</description><author>AJ Stuyvenberg</author><pubDate>Thu, 03 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://aaronstuyvenberg.com/posts/lambda-timeout-doom-loop</guid></item><item><title>Time to Write More (Again)</title><link>https://fractaldragon.net/posts/2024-10-03-time-to-write_more.html</link><description>&amp;lt;!doctype html&gt;

    
        
        
        
        Time to Write More (Again) - Recursive Ramblings
        
    
    
        &lt;header&gt;
            &lt;div class="logo"&gt;
                &lt;a href="../"&gt;Recursive Ramblings&lt;/a&gt;
            &lt;/div&gt;
            &lt;nav&gt;
                &lt;a href="../about.html"&gt;About&lt;/a&gt;
                &lt;a href="../contact.html"&gt;Contact&lt;/a&gt;
                &lt;a href="../archive.html"&gt;Archive&lt;/a&gt;
		&lt;!-- &lt;a href="/tags/"&gt;Tags&lt;/a&gt; --&gt;
		&lt;a href="../now"&gt;Now&lt;/a&gt;
		&lt;a href="../TIL/"&gt;TIL&lt;/a&gt;
		&lt;!--
		    &lt;a href="/atom.xml"&gt;Atom&lt;/a&gt;
                    &lt;a href="/rss.xml"&gt;RSS&lt;/a&gt;
		    --&gt;
            &lt;/nav&gt;
        &lt;/header&gt;

        
          &lt;h1&gt;Time to Write More (Again)&lt;/h1&gt;
	  

            &lt;article&gt;
    &lt;section class="header"&gt;
      Posted on 2024-10-03
      
      
      &lt;div class="row tag-block"&gt;
        
        &lt;span&gt; &lt;a href="../tags/100DaysToOffload.html"&gt;[100DaysToOffload]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/writing.html"&gt;[writing]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/blogging.html"&gt;[blogging]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/note-taking.html"&gt;[note-taking]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/Joplin.html"&gt;[Joplin]&lt;/a&gt;&lt;/span&gt;
        
        &lt;span&gt; &lt;a href="../tags/journaling.html"&gt;[journaling]&lt;/a&gt;&lt;/span&gt;
        
      &lt;/div&gt;
      
      
      &lt;br /&gt;
      Tim Lavoie
      
    &lt;/section&gt;
    &lt;section&gt;
        &lt;h2 id="ugh-it-really-has-been-a-while-hasnt-it"&gt;Ugh, it really has been a while, hasn’t it?&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;tl;dr: Back at it, because I haven’t been in ages. Possibly a regular thing now, if I’ve made it easier (enough)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I took a cruise past my own site recently, and realized that I hadn’t
done a damn bit of work here in a stupidly long time. Why is that? Is
there nothing to talk about? I don’t think that’s it, it’s really just
laziness. Not even the good kind, like what it can do for you in a
program, but mere idle distraction.&lt;/p&gt;
&lt;p&gt;What I thought I would do, is to again try and kick off one of those
&lt;a href="https://100daystooffload.com/"&gt;#100DaysToOffload&lt;/a&gt; things. That is,
I’ll try to get something posted as often as possible, with the idea
of having at least 100 new posts within a year. I’ve started this
&lt;a href="../posts/2020-12-06-100_posts.html"&gt;before&lt;/a&gt;, but flamed out fairly
quickly. What is supposed to be important is that you’re doing it, for
yourself, on your own site. I over-thinking it, and not wanting to
start until it was complete in my head.&lt;/p&gt;
&lt;p&gt;The last time I’d scribbled
&lt;a href="../posts/2023-06-05_notes_value_second_brain.html"&gt;something&lt;/a&gt;, the
topic was notes. While I haven’t written anything publicly here since,
I have in fact continued that one process. That is progress!&lt;/p&gt;
&lt;p&gt;&lt;a href="https://joplinapp.org/"&gt;Joplin&lt;/a&gt; continues to function very well for
me, and there are a few aspects that I find useful here.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;First, it’s always available. I have it on my phone, and on various
computers, centrally synced.&lt;/li&gt;
&lt;li&gt;I own my own data, in that the sync setup is strictly mine. It’s not
being mined for AI bullshit, anbd I don’t need Copilot / ChatGPT /
whatever telling me what I might want to type next.&lt;/li&gt;
&lt;li&gt;Tags help me think of a barebones bit of topical organization, and
universal search of the data fills in the rest.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What I’ve done is to create a separate notebook in Joplin that is
specific to a daily journal. The point is to make writing something
down a daily habit, by making it &lt;em&gt;easy&lt;/em&gt;. When I sit down at my
computer at the start of the day, I click on the “Journal” notebook,
and run a macro that creates a new skeleton page with today’s date and
common sorts of entries.&lt;/p&gt;
&lt;figure&gt;
&lt;img alt="(daily journal template)" src="../images/sample_joplin_journal_template.png" /&gt;
&lt;figcaption&gt;(daily journal template)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Ignore the repeated date entry in the list, this one is just an
example, and I have a note for today already. The point is that a couple
keystrokes gives me a new note at hand, with today’s date and a bit of
a prompt. Anything that doesn’t get used, say “Push-ups” &lt;em&gt;(cough)&lt;/em&gt;
just gets deleted for that day.&lt;/p&gt;
&lt;p&gt;In something like Zettelkasten style, I’ll simply scribble in there
throughout the day. Occasionally, I’ll copy bits over to my regular
“Notes” notebook (sad naming, I know), where they’re part of my usual
setup like a personal wiki of cross-referenced notes.&lt;/p&gt;
&lt;p&gt;The journal is really just for me, capturing “shower thoughts” ideas,
health, bits of the weather, anything. Being searchable means I can
always find it later, and I could easily export it if I decide I need
a different tool. (In fact, I’ve done this sort of transition before,
going &lt;em&gt;to&lt;/em&gt; Joplin.) It has been over a year now though, and appears to
have borne fruit. This should translate into much easier topic
selection when thinking of something to write publicly about, without
having to reach for a completely blank slate every time.&lt;/p&gt;
    &lt;/section&gt;
    
    &lt;section id="isso-thread"&gt;&lt;/section&gt;
&lt;/article&gt;

        

        &lt;footer&gt;
	    &lt;a href="https://notbyai.fyi/"&gt;&lt;img src="../images/written_by_human.png" /&gt;&lt;/a&gt;
            Site generated using the awesome
            &lt;a href="https://jaspervdj.be/hakyll"&gt;Hakyll&lt;/a&gt;
        &lt;/footer&gt;</description><author>Recursive Ramblings</author><pubDate>Thu, 03 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://fractaldragon.net/posts/2024-10-03-time-to-write_more.html</guid></item><item><title>Quick Ways to Disable GitHub Actions Workflows Without Deletion</title><link>https://www.safjan.com/quick-ways-to-disable-github-actions-workflows-without-deletion/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=safjan-blog</link><description>&lt;p&gt;Learn three quick methods to temporarily disable GitHub Actions workflows without deleting them, including commenting out code, using manual triggers, and adding conditional logic.&lt;/p&gt;</description><author>Krystian Safjan's Blog</author><pubDate>Thu, 03 Oct 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://www.safjan.com/quick-ways-to-disable-github-actions-workflows-without-deletion/?utm_source=rss&amp;utm_medium=feed&amp;utm_campaign=safjan-blog</guid></item><item><title>Set Up Rails Activestorage With Azure Securely</title><link>https://caiustheory.com/set-up-rails-activestorage-with-azure-securely/</link><description>&lt;p&gt;&lt;a href="https://rubyonrails.org"&gt;Ruby on Rails&lt;/a&gt; has built-in support for managing uploaded files with &lt;a href="https://github.com/rails/rails/tree/main/activestorage#readme"&gt;ActiveStorage&lt;/a&gt;, which both cleans up your application code and acts as an abstraction over different storage backends. Azure Storage is one of the supported backends, but configuring it securely can take a little figuring out.&lt;/p&gt;
&lt;p&gt;The most sensible way I&amp;rsquo;ve found to have it configured is with files stored having no public access, but allowing temporary access via signed URLs for both uploads &amp;amp; downloads. ActiveStorage happily generates these URLs for us which makes it easy for developers to use and transparent to users, whilst keeping their data as secure and protected as possible. From a technical point of view, this also allows upload and download between Azure Storage and the user&amp;rsquo;s browser, without proxying all the data through your Rails app.&lt;/p&gt;
&lt;h3 id="azure-setup"&gt;Azure setup&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;ll be storing the files in Azure as blobs within &lt;a href="https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction"&gt;Azure Blob Storage&lt;/a&gt;. Blobs live within a Storage Container which lives within a Storage Account, which is created in a specific geographical region and Resource Group.&lt;/p&gt;
&lt;p&gt;To create the above, we&amp;rsquo;ll need access to an Azure Subscription with permissions to create the resources. (Most of the time you will already be an admin on the subscription, so can ignore checking this. If you get permission errors creating the below, you&amp;rsquo;ll need to go talk to your admin.)&lt;/p&gt;
&lt;p&gt;Most resources in Azure need to live within a Resource Group, which it&amp;rsquo;s suggested you use to group related resources together. If there isn&amp;rsquo;t already a resource group for your application resources, create one.&lt;/p&gt;
&lt;p&gt;Next we need to create a Storage Account in the main location for the app and nested in the Resource Group from above. The following settings are split across multiple pages, click &amp;ldquo;Next&amp;rdquo; to advance to next bit of the form:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage account name&lt;/strong&gt;: 3-24 numbers/lowercase letters only, unique across all storage accounts globally in Azure. Good luck. (eg, &lt;code&gt;st123someappproduksth&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Region&lt;/strong&gt;: choose for your app location (eg, UK South)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Standard (general purpose v2 account)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redundancy&lt;/strong&gt;: Zone-redundant storage (ZRS) if possible. (Some regions don&amp;rsquo;t support it, choose Locally-redundant storage (LRS) in those.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enable infrastructure encryption&lt;/strong&gt;: Check the box, it&amp;rsquo;s free.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tags&lt;/strong&gt;: tag the resource according to your internal conventions/policies (eg, &lt;code&gt;terraform:false&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Create the Storage Account and wait for it to complete. Next we&amp;rsquo;ll create the Storage Container nested under the Storage Account which is where our actual files will be uploaded to. Find the Storage Account we created in the portal and click &amp;ldquo;Containers&amp;rdquo; in the sidebar, then &amp;ldquo;+ Container&amp;rdquo; above the table listing the containers.&lt;/p&gt;
&lt;p&gt;Add a sensible name for the Container, which is easier because there are &lt;a href="https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata#container-names"&gt;fewer restrictions on this name&lt;/a&gt;. (eg, &lt;code&gt;dragon-myapp-production-uksouth-activestorage&lt;/code&gt;.) Create the container and wait for that to complete.&lt;/p&gt;
&lt;p&gt;Navigate back to viewing the Storage Account and select &amp;ldquo;Access Keys&amp;rdquo; in the sidebar. Make a note of the Key value (for key1) for use later configuring Rails. If you need to rotate the access keys later without causing an outage, you can update the app to use the Key from key2 and then rotate the key for key1 from the UI.&lt;/p&gt;
&lt;p&gt;The final configuration we might need to set in Azure is allowing direct uploads from our app to the container. Due to this being client-side requests we need the Cross-Origin Resource Sharing (CORS) headers to allow this from Azure&amp;rsquo;s side. Without these in place you will get permissions errors from browsers trying to make requests directly to Azure. If you&amp;rsquo;re not using ActiveStorage&amp;rsquo;s Direct Upload feature, you can skip this.&lt;/p&gt;
&lt;p&gt;Find &amp;ldquo;Resource Sharing (CORS)&amp;rdquo; in the sidebar for the Storage Account. Add a new entry to the &amp;ldquo;Blob service&amp;rdquo; table:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allowed origins: comma-separated list of all your app domains using this storage.&lt;/li&gt;
&lt;li&gt;Allowed methods: &lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;OPTIONS&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Allowed headers: *&lt;/li&gt;
&lt;li&gt;Exposed headers: *&lt;/li&gt;
&lt;li&gt;Max age: 0&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Click out of the fields and the configuration will be saved automagically. (Little green ticks appear in the fields for some visual feedback.)&lt;/p&gt;
&lt;h3 id="rails-setup"&gt;Rails setup&lt;/h3&gt;
&lt;p&gt;We can follow the Rails documentation for &lt;a href="https://guides.rubyonrails.org/active_storage_overview.html#setup"&gt;setting up ActiveStorage&lt;/a&gt;, ensures you have the migrations for the required database tables installed and the framework is loaded in your application.&lt;/p&gt;
&lt;p&gt;Follow the rails docs &lt;a href="https://guides.rubyonrails.org/active_storage_overview.html#microsoft-azure-storage-service"&gt;for configuring &lt;code&gt;config/storage.yml&lt;/code&gt; with Azure&lt;/a&gt;, and pulling &lt;code&gt;azure-storage-blob&lt;/code&gt; into your app. (In Rails 8.1 this will be &lt;code&gt;azure-blob&lt;/code&gt; instead, see &lt;a href="https://testdouble.com/insights/azure-blob-a-new-ruby-gem-for-azure-blob-storage"&gt;test double&amp;rsquo;s post on the subject for more information&lt;/a&gt;) I suggest storing the key in Rails credentials (or however you inject secrets into your rails app), and creating a new block in &lt;code&gt;storage.yml&lt;/code&gt; for Azure storage. Using credentials per-environment makes using a different storage account for Staging &amp;amp; Production easy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;azure_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AzureStorage"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage_account_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;%= Rails.application.credentials.dig(:azure_storage, :storage_account_name) %&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage_access_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;%= Rails.application.credentials.dig(:azure_storage, :container_name) %&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Edit the credentials for the given environment (eg, &lt;code&gt;bin/rails credentials:edit --environment=production&lt;/code&gt;) and enter the values we got from Azure above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;azure_storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage_account_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"st123someappproduksth"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage_access_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Qm9yZWQgeWV0PyBGdWNraW5nIGJhbGxhY2hlIGlubml0Lgo="&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dragon-myapp-production-uksouth-activestorage"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Update the appropriate &lt;code&gt;config/environments/*.rb&lt;/code&gt; file for the environments you want to use this Azure storage, setting &lt;code&gt;config.active_storage.service&lt;/code&gt; to the key of the block in &lt;code&gt;storage.yml&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;active_storage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;:azure_storage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Deploy your changes, and you&amp;rsquo;re good to start using ActiveStorage to manage your uploaded files. Ensure the ActiveStorage JS is added to your frontend (depends on which asset pipeline flavour the app is configured to use, should be there by default) and you won&amp;rsquo;t be sending all files through your Rails backend, saves you some bandwidth and CPU cycles.&lt;/p&gt;</description><author>Caius Theory</author><pubDate>Thu, 03 Oct 2024 00:28:49 GMT</pubDate><guid isPermaLink="true">https://caiustheory.com/set-up-rails-activestorage-with-azure-securely/</guid></item><item><title>How Talking Over A Wall Changed My Direction As A Programmer</title><link>https://thecodist.com/how-talking-over-a-wall-changed-my-direction-as-a-programmer/</link><description>&lt;p&gt;I started my programming career in October 1981 at a large defense contractor (GD). At the time, my goal was to work for a couple of years and then continue my education with a Ph.D. in Chemistry (I had already been accepted).&lt;/p&gt;&lt;p&gt;The office I worked in was a&lt;/p&gt;</description><author>The Codist</author><pubDate>Wed, 02 Oct 2024 21:21:51 GMT</pubDate><guid isPermaLink="true">https://thecodist.com/how-talking-over-a-wall-changed-my-direction-as-a-programmer/</guid></item><item><title>Seed Valuations Aren’t Valuations</title><link>https://whoisnnamdi.com/seed-valuations/</link><description>It’s not obvious what drives them</description><author>Who is Nnamdi?</author><pubDate>Wed, 02 Oct 2024 20:51:14 GMT</pubDate><guid isPermaLink="true">https://whoisnnamdi.com/seed-valuations/</guid></item><item><title>Cybersyn's Product Release</title><link>https://magis.substack.com/p/cybersyns-product-release</link><description>My startup's beta product for Consumer Spending data</description><author>Magis</author><pubDate>Wed, 02 Oct 2024 18:12:00 GMT</pubDate><guid isPermaLink="true">https://magis.substack.com/p/cybersyns-product-release</guid></item><item><title>We'll buy back your Typewriter ... for Uncle Sam!</title><link>https://pncnmnp.github.io/blogs/typewriter-uncle-sam.html</link><description>In this blog post, we will explore another poster I recently bought, which highlights Smith-Corona's campaign to buy back typewriters for the U.S. government due to wartime shortages during World War II.</description><author>Parth Parikh's Blog</author><pubDate>Wed, 02 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://pncnmnp.github.io/blogs/typewriter-uncle-sam.html</guid></item><item><title>Linux Assembly Part 2: Declaring Data</title><link>https://cookie.engineer/weblog/articles/linux-assembly-part-2-declaring-data.html</link><description>Learn Linux assembly to declare data and reserve memory.</description><author>Cookie Engineer's Web Log</author><pubDate>Wed, 02 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://cookie.engineer/weblog/articles/linux-assembly-part-2-declaring-data.html</guid></item><item><title>Decentralised Open Indexes for Discovery (DOID)</title><link>https://nadh.in/blog/decentralised-open-indexes/</link><description>&lt;p&gt;TLDR; A conceptual and technical framework for resource discovery on the WWW using decentralised, open, machine-readable indexes as the building block, free of eroding quality and gatekeeping by &lt;em&gt;BigSearch™&lt;/em&gt; and &lt;em&gt;BigPlatform™&lt;/em&gt;, whose goals are not quality, but revenue.&lt;/p&gt;</description><author>Blog on Kailash Nadh / Personal homepage</author><pubDate>Wed, 02 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nadh.in/blog/decentralised-open-indexes/</guid></item><item><title>Legalize Life (and Living)</title><link>https://taylor.town/oh-possession</link><description>Any fruits [or Mexicans] in your car today?</description><author>taylor.town</author><pubDate>Wed, 02 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/oh-possession</guid></item><item><title>2024-10-01-002</title><link>https://srijan.ch/notes/2024-10-01-002</link><description>I have been using #karousel on #KDE for several weeks, and yesterday shifted to #PaperWM on #GNOME. Took some time to configure things like I wanted, but it's much smoother than karousel (and fancier). Overall, I like the scrolling tiling pane paradigm. I realized I've been manually doing something like this using workspaces with 1-2 windows per workspace with two keybindings - one to change …</description><author>Srijan Choudhary, all posts</author><pubDate>Wed, 02 Oct 2024 01:50:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2024-10-01-002</guid></item><item><title>Programatically invoke rake task with --trace enabled</title><link>https://caiustheory.com/programatically-invoke-rake-task-with--trace-enabled/</link><description>&lt;p&gt;&lt;a href="https://ruby.github.io/rake/"&gt;Rake&lt;/a&gt; is a Make-like program implemented in Ruby &lt;em&gt;(to quote the website.)&lt;/em&gt; It contains tasks written in ruby code you can invoke from the command line. These can run prequisite tasks first too, in this case we configure the &lt;code&gt;setup&lt;/code&gt; task to be invoked before the &lt;code&gt;work&lt;/code&gt; task is ever invoked.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:setup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Setting up"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;work&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="ss"&gt;:setup&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Doing work"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ rake work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Setting up
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Doing work
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Rake also supports &lt;code&gt;--trace&lt;/code&gt; as an argument which outputs a bunch of debugging information, allowing you to see which tasks have been executed in which order and how often they were invoked.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ rake --trace work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke work &lt;span class="o"&gt;(&lt;/span&gt;first_time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke setup &lt;span class="o"&gt;(&lt;/span&gt;first_time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Execute setup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Setting up
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Execute work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Doing work
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;From the &lt;code&gt;--trace&lt;/code&gt; output we can see calling the same task multiple times in the same rake process doesn&amp;rsquo;t execute the task multiple times, but it is invoked the correct number of times.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ rake --trace work setup work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke work &lt;span class="o"&gt;(&lt;/span&gt;first_time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke setup &lt;span class="o"&gt;(&lt;/span&gt;first_time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Execute setup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Setting up
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Execute work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Doing work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke setup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke work
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;For performance reasons (&lt;a href="https://rubyonrails.org"&gt;Rails&lt;/a&gt; apps can take multiple seconds to start a new process), we might want to run multiple tasks within the same &lt;code&gt;rake&lt;/code&gt; process.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:create_assets&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"Created some assets"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;We&amp;rsquo;re now stuck in a catch-22 from the shell in terms of tracing however, if we run both tasks in the same process we can only trace both, or trace neither.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ rake --trace create_assets work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke create_assets &lt;span class="o"&gt;(&lt;/span&gt;first_time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Execute create_assets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Created some assets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke work &lt;span class="o"&gt;(&lt;/span&gt;first_time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke setup &lt;span class="o"&gt;(&lt;/span&gt;first_time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Execute setup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Setting up
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Execute work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Doing work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ rake create_assets work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Created some assets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Setting up
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Doing work
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If we&amp;rsquo;ve previously been calling multiple tasks in different commands and only tracing some of them, we want to maintain the same output in our CI system logs but also reap the performance benefit of not booting the app multiple times over. This means having tracing enabled for some tasks, but not others which we can&amp;rsquo;t do from the shell.&lt;/p&gt;
&lt;p&gt;Luckily there&amp;rsquo;s another mechanism to invoke multiple rake tasks, we define a new rake task that can then call the other rake tasks and setup tracing before calling the last one by &lt;a href="https://github.com/ruby/rake/blob/4538838a4b9d2cbfa1e231716a2183e65241b52e/lib/rake/application.rb#L613-L622"&gt;mimicking the internals of &lt;code&gt;rake --trace&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(&lt;code&gt;Rake::Task.[]&lt;/code&gt; lets you find rake tasks by their labels, and then &lt;code&gt;Rake::Task#invoke&lt;/code&gt; calls the code defined for the task as if you&amp;rsquo;d run &lt;code&gt;rake whatever&lt;/code&gt; on the cli.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;task&lt;/span&gt; &lt;span class="ss"&gt;:ci_perform&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"create_assets"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Enable `--trace` programatically for remaining tasks invoked&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backtrace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace_output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="vg"&gt;$stderr&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="no"&gt;Rake&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Task&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"work"&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;invoke&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et voila, we get our trace output for the &lt;code&gt;work&lt;/code&gt; task but not until that&amp;rsquo;s invoked.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ rake ci_perform
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Created some assets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke work &lt;span class="o"&gt;(&lt;/span&gt;first_time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Invoke setup &lt;span class="o"&gt;(&lt;/span&gt;first_time&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Execute setup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Setting up
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;** Execute work
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Doing work
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description><author>Caius Theory</author><pubDate>Wed, 02 Oct 2024 01:42:00 GMT</pubDate><guid isPermaLink="true">https://caiustheory.com/programatically-invoke-rake-task-with--trace-enabled/</guid></item><item><title>The Midnight Library by Matt Haig</title><link>https://apurva-shukla.me/bookshelf/the-midnight-library/</link><description>⭐ ⭐ ⭐ ⭐ Matt Haig’s Midnight Library is a fable following the afterlife of Nora struck in between life and death in a mysterious shrouded…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Wed, 02 Oct 2024 01:20:18 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/the-midnight-library/</guid></item><item><title>Portable gaming with the Retroid Pocket 4 Pro</title><link>https://utf9k.net/blog/retroid-pocket-4-pro/</link><description>I finally found a handheld that I really enjoy</description><author>utf9k</author><pubDate>Wed, 02 Oct 2024 00:40:00 GMT</pubDate><guid isPermaLink="true">https://utf9k.net/blog/retroid-pocket-4-pro/</guid></item><item><title>Raw Markdown Pages</title><link>https://www.danielcorin.com/til/hugo/raw-markdown-pages/</link><description>Raw Markdown Pages</description><author>Thought Eddies</author><pubDate>Tue, 01 Oct 2024 23:02:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/hugo/raw-markdown-pages/</guid></item><item><title>Ad hoc tools for gathering prompt context</title><link>https://austinhenley.com/blog/promptcontext.html</link><description>&lt;a href="https://austinhenley.com/blog/promptcontext.html"&gt;https://austinhenley.com/blog/promptcontext.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Tue, 01 Oct 2024 06:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/promptcontext.html</guid></item><item><title>So you want to migrate to Kubernetes: observations from a software developer</title><link>https://ounapuu.ee/posts/2024/10/01/kubernetes/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/10/01/kubernetes/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;Kubernetes: everyone wants to do it, regardless of their scale and business objectives.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Common justifications include better scalability, cost savings, standardization and being super modern and stuff. It&amp;rsquo;s
the future!&lt;/p&gt;
&lt;p&gt;In my personal experience, Kubernetes is far from the magical uptime machine that a lot of people think it is, and
migrating it to it comes with a lot of hidden costs and potential downtime.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m not a Kubernetes expert, but I&amp;rsquo;ve been involved in a few Kubernetes migration projects and I have
&lt;em&gt;&lt;strong&gt;opinions.&lt;/strong&gt;&lt;/em&gt; Here are all the learnings and observations that I&amp;rsquo;ve personally witnessed.&lt;/p&gt;
&lt;h3 id="migrations-are-complex"&gt;Migrations are complex&lt;/h3&gt;
&lt;p&gt;For most companies, using Kubernetes will mean planning and executing a migration project.&lt;/p&gt;
&lt;p&gt;Assumptions will be made, estimates communicated and then the work begins. 90% of the migration will likely go
relatively smoothly, but the last
10% will result in the migration project blowing past any initial estimates that you had.&lt;/p&gt;
&lt;p&gt;There will always be those teams and services that require more time due to conflicting priorities or unexpected
technical nuances popping up during testing.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s the long tail that gets you.&lt;/p&gt;
&lt;h3 id="kubernetes-is-complex"&gt;Kubernetes is complex&lt;/h3&gt;
&lt;p&gt;Kubernetes is so complex that most people point you towards managed Kubernetes clusters provided by the big cloud
providers
as a starting point.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; To me, this is the best indication that we&amp;rsquo;ve lost the plot.&lt;/p&gt;
&lt;p&gt;Kubernetes is an abstraction layer on an already complicated
stack, and &lt;a href="https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/"&gt;abstractions tend to leak&lt;/a&gt; at the
most inconvenient time.&lt;/p&gt;
&lt;p&gt;There is nothing wrong about running plain old virtual machines as container hosts and scaling them vertically. Load
balancers and containers
are a stable and reliable technology by now, and individual servers have made a big
leap in performance over the past decade.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;re going to have to know about the fundamentals of where your service is running either way, so you might as well
keep the stack simple, understandable and easily debuggable, avoiding all the extra complexity.&lt;/p&gt;
&lt;p&gt;There is no shame in &lt;a href="https://boringtechnology.club/"&gt;choosing boring technology.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Shout-out to all the madlads who run Kubernetes at home &lt;em&gt;&lt;strong&gt;for fun.&lt;/strong&gt;&lt;/em&gt; I respect the hustle.&lt;/p&gt;
&lt;h3 id="kubernetes-will-only-start-making-sense-at-scale"&gt;Kubernetes will only start making sense at scale&lt;/h3&gt;
&lt;p&gt;If your company doesn&amp;rsquo;t have a fully staffed platform team (6-8 full-time employees), then you probably don&amp;rsquo;t need
Kubernetes.&lt;/p&gt;
&lt;p&gt;If you do, then you can start considering it, but know that it won&amp;rsquo;t magically solve every issue that you have in your
tech organization. Your time might be better spent on tackling &lt;em&gt;those&lt;/em&gt; issues first.&lt;/p&gt;
&lt;p&gt;Kubernetes is great if you want to standardize how your workloads run, and with additional tooling and setup you can
end up with a pretty neat system where developers can set up new services on their own and easily monitor them
using your observability stack (Grafana, Prometheus etc.).&lt;/p&gt;
&lt;p&gt;This requires a lot of effort though, from both your platform team and developers. This effort will be unreasonably high
for small startups and organizations, and my guesstimate is that using Kubernetes will start making sense if you have
100+ developers in your tech organization.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re a small team that has a setup that works for you, then continue using it. You&amp;rsquo;re doing great!&lt;/p&gt;
&lt;p&gt;If you only need a few Kubernetes features, such as autoscaling, health checks and rolling deployments, then you can
probably find a simple solution that works on your existing stack.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re just starting up, then don&amp;rsquo;t use Kubernetes. My recommendation is to start
with a stupid simple stack that you know really well and scale it up vertically for as long as possible. Once that setup
does not work for you, you will probably have enough money and people to do the Kubernetes migration. It&amp;rsquo;s a good
problem to have!&lt;/p&gt;
&lt;h3 id="let-your-developers-learn-kubernetes-before-migrating-to-it"&gt;Let your developers learn Kubernetes before migrating to it&lt;/h3&gt;
&lt;p&gt;If you skip this part, then expect a lot of questions, blocking issues, missed deadlines, hasty debugging, lost
productivity and multiple production outages.&lt;/p&gt;
&lt;p&gt;When I first dealt with Kubernetes, I had no idea what I was doing. I barely got to search what was the difference
between pods and nodes, how to package applications into containers and what the hell an ingress was&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;. There was no
formal training or opportunities to take a few days to play around with Kubernetes before rolling it out to production.
I had to make sure my other work commitments got done in parallel.&lt;/p&gt;
&lt;p&gt;It &lt;em&gt;&lt;strong&gt;sucked&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I eventually got better at working with Kubernetes, mainly as a result of learning from production outages. This is
also training, but much more expensive compared to simply giving developers the opportunity to learn and experiment
in a sandbox.&lt;/p&gt;
&lt;p&gt;After doing most of &lt;a href="https://github.com/indrekots/kubernetes-the-much-harder-way"&gt;&amp;ldquo;Kubernetes The Much Harder Way&amp;rdquo;&lt;/a&gt;, I
have a
vague understanding of what I&amp;rsquo;m doing, but if the Kubernetes cluster were to completely fall over in an unexpected way,
I would still have no idea on how to even approach fixing it, or how to make sure that the cluster is properly secured.&lt;/p&gt;
&lt;p&gt;One day long Kubernetes workshop organized by the company can go a long way in helping everyone get up to speed.
Just organize one &lt;em&gt;&lt;strong&gt;before&lt;/strong&gt;&lt;/em&gt; the migration.&lt;/p&gt;
&lt;h3 id="your-overworked-developers-wont-like-it"&gt;Your overworked developers won&amp;rsquo;t like it&lt;/h3&gt;
&lt;p&gt;During my 8+ years as a software developer, there was a push for developers to be full stack and to embrace the
operational side. I don&amp;rsquo;t have anything against that, developers should be responsible for what they deploy and observe
the behaviour of their services diligently.&lt;/p&gt;
&lt;p&gt;However, I&amp;rsquo;ve also learned that a good chunk of developers don&amp;rsquo;t want to mess with the full stack and want to focus on
their area of responsibility, which may involve more product-focused work. Throw in some inefficient meetings,
absurdly high expectations from the business side, and the time and tolerance for handling anything else goes way down.&lt;/p&gt;
&lt;p&gt;The cognitive capacity for the average developer is limited. If your developers are already on that limit, and you
decide that &lt;strong&gt;&lt;em&gt;we need some Kubernetes&lt;/em&gt;&lt;/strong&gt; and developers need to be responsible for their own deployments, then you need
to expect some resistance and contempt towards you.&lt;/p&gt;
&lt;p&gt;Even before you get to the Kubernetes part, you may also have to make sure that developers know the fundamentals about
where their service runs and what amount of resource consumption is appropriate for their service. Turns
out that this is not a given, especially in a fast-growth environment where teams and ownerships change often.&lt;/p&gt;
&lt;p&gt;Oh, and developers might get very angry with you as every
Kubernetes-related frustration will be attributed to your platform team, even if it&amp;rsquo;s an issue they themselves caused.
It&amp;rsquo;s not fair, but it&amp;rsquo;s how it may play out.&lt;/p&gt;
&lt;h3 id="your-application-code-has-made-assumptions-about-the-platform-its-running-on"&gt;Your application code has made assumptions about the platform it&amp;rsquo;s running on&lt;/h3&gt;
&lt;p&gt;If you don&amp;rsquo;t have expert knowledge about the service that you&amp;rsquo;re about to migrate to Kubernetes, then you&amp;rsquo;ll likely
miss any assumptions that have been made in the application code itself.&lt;/p&gt;
&lt;p&gt;Most common one is the assumption that there exists only one instance of your service at any time. You can
lift-and-shift
it to Kubernetes &lt;em&gt;as-is&lt;/em&gt;, but then you won&amp;rsquo;t be taking advantage of any scalability benefits that Kubernetes offers, so
what&amp;rsquo;s the point?&lt;/p&gt;
&lt;p&gt;There was also a case where a service was relying on local storage for temporarily storing tasks that had to be
picked up later. This made perfect sense on a virtual machine, but on Kubernetes the storage on the pods
is ephemeral, and pods have a habit of restarting for all sorts of reasons. This issue went unnoticed for quite a while
and
only became known after someone familiar with the service asked about it.
Adding a &lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/"&gt;persistent volume&lt;/a&gt;
fixed this issue.&lt;/p&gt;
&lt;p&gt;Some libraries and solutions can also make assumptions about the number of instances that your service has, or the
internal IP addresses that point to your service being static and predictable. Kubernetes breaks all of those
assumptions.&lt;/p&gt;
&lt;h3 id="you-still-need-a-platform-team"&gt;You still need a platform team&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve seen claims that using Kubernetes will mean that you&amp;rsquo;ll need fewer people on your platform team, especially if you
use a managed Kubernetes offering.&lt;/p&gt;
&lt;p&gt;The reality is that you still need someone to make sure that even a managed Kubernetes instance stays up and running.
This involves mundane work, such as making sure that updates are applied correctly without breaking every workload,
or making sure that additional tooling bolted on to your Kubernetes cluster doesn&amp;rsquo;t wreck the services that are running
on it.&lt;/p&gt;
&lt;p&gt;Before the migration, your platform team answered questions and requests from developers, and wrangled whatever
infrastructure you had running.&lt;/p&gt;
&lt;p&gt;During the migration, your platform team will be answering questions and requests for both setups while also setting up
Kubernetes and related infrastructure-as-code solutions, and unless you brought in more people before the migration,
they&amp;rsquo;ll be overworked.&lt;/p&gt;
&lt;p&gt;After the migration, your platform team will still be answering questions and requests, maintaining whatever
infrastructure-as-code
solutions you put in place, and making sure that Kubernetes stays running, which seems to take about the same number
of people as before, if not more.&lt;/p&gt;
&lt;p&gt;If you managed to avoid burning out any engineers during the migration, then that&amp;rsquo;s great!&lt;/p&gt;
&lt;p&gt;If you managed to reduce headcount &lt;em&gt;after&lt;/em&gt; a Kubernetes migration and it did not bite you in the ass years after the
fact, then please do let me know.&lt;/p&gt;
&lt;h3 id="kubernetes-wont-fix-your-legacy-monolith"&gt;Kubernetes won&amp;rsquo;t fix your legacy monolith&lt;/h3&gt;
&lt;p&gt;Kubernetes works really well with small services that start up within seconds and use relatively few resources.&lt;/p&gt;
&lt;p&gt;The start-up time of your monolith is probably measured in minutes, and it likes to use all the CPU cores and RAM that
you
give it.&lt;/p&gt;
&lt;p&gt;It can still run on Kubernetes, but certain aspects, such as scaling up fast in response to a spike in load, won&amp;rsquo;t work
due to the long start-up time, or due to existing Kubernetes nodes not being able to accommodate your monolith without
slowly starting up new nodes. By the time more instances of your service start up, that temporary increase in load
might have already passed. Your performance still sucks and your resource usage graphs look like a poorly maintained
saw.&lt;/p&gt;
&lt;p&gt;Your platform team will also be unhappy with these types of services as these big resource-hungry monoliths tend to
require the use of bigger nodes, and they might even end up impacting neighboring pods if configured improperly.&lt;/p&gt;
&lt;p&gt;If you have set up tooling to ship service logs from your pods to a centralized location, then you might also find
that your high-traffic monolith is logging so much that the tooling can&amp;rsquo;t keep up, resulting in logs going missing.
The root cause can be something as basic
as &lt;a href="https://vector.dev/docs/reference/configuration/sources/kubernetes_logs/#glob_minimum_cooldown_ms"&gt;a default configuration value not working out&lt;/a&gt;
for your &lt;em&gt;thicc&lt;/em&gt; monolith, but by the time you get to that discovery, you&amp;rsquo;ll have wasted a good number of hours or days
of productive work time.&lt;/p&gt;
&lt;h3 id="kubernetes-wont-magically-fix-your-performance-issues"&gt;Kubernetes won&amp;rsquo;t magically fix your performance issues&lt;/h3&gt;
&lt;p&gt;Autoscaling is one of the features that a lot of Kubernetes users like.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;re having lunch and your service got really popular all of a sudden? No problem, your properly configured
&lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/"&gt;HorizontalPodAutoscaler&lt;/a&gt; can take care of
it!&lt;/p&gt;
&lt;p&gt;Autoscaling can save your butt, but it can also introduce additional issues.&lt;/p&gt;
&lt;p&gt;For example, deploying a new version of your service can fail
because you have too many instances of the service running. Databases, such
as &lt;a href="https://www.postgresql.org/"&gt;PostgreSQL&lt;/a&gt;,
have a limited number of database connections available. Each instance of your service using up N database connections.
If you don&amp;rsquo;t account for deployments or autoscaling scenarios, then the new instances
will fail to start up because they cannot establish new database connections. It&amp;rsquo;s a good idea to have a few instances'
worth of database connections set aside as a buffer.&lt;/p&gt;
&lt;p&gt;Unless you&amp;rsquo;re actually limited by physical constraints, such as CPU time, memory and network bandwidth, then Kubernetes
is unlikely to fix any performance issues. You&amp;rsquo;re better off profiling your application, network and database
performance first and making sure that your observability stack gives you enough information to troubleshoot
performance issues.&lt;/p&gt;
&lt;h3 id="kubernetes-is-not-a-magical-uptime-machine"&gt;Kubernetes is not a magical uptime machine&lt;/h3&gt;
&lt;p&gt;It really isn&amp;rsquo;t.&lt;/p&gt;
&lt;p&gt;At some point, you will have downtime because of a Kubernetes configuration issue, taking down your whole service.&lt;/p&gt;
&lt;p&gt;Sometimes you&amp;rsquo;ll involve additional tooling to make working with Kubernetes easier. That can also horrifically backfire
due to &lt;a href="https://ounapuu.ee/posts/2024/04/04/helm-rollbljat/"&gt;circumstances not under your control.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;ll probably have system-wide latency spikes because a critical service got its pods restarted one by one,
and the new pods need to warm up their caches again. This is especially true
for &lt;a href="https://en.wikipedia.org/wiki/Java_virtual_machine"&gt;JVM&lt;/a&gt;-based services.&lt;/p&gt;
&lt;p&gt;Misconfigured tooling can wreak havoc on your Kubernetes cluster. It&amp;rsquo;s not fun to troubleshoot why all your pods
suddenly disappeared, only to find out later that &lt;a href="https://karpenter.sh/"&gt;Karpenter&lt;/a&gt; went on a pod massacre.&lt;/p&gt;
&lt;h3 id="vertical-scaling-can-go-a-long-way"&gt;Vertical scaling can go a long way&lt;/h3&gt;
&lt;p&gt;If your current stack has a load balancer and a few containers, and you&amp;rsquo;re not doing anything too inefficient,
then you can probably scale up vertically for a very long time.&lt;/p&gt;
&lt;p&gt;Servers have made a big leap in performance and capability, resulting in
machines with 128+ CPU cores, &lt;em&gt;terabytes&lt;/em&gt; of fast memory and lots of room for adding ridiculously fast SSD-based
storage.&lt;/p&gt;
&lt;p&gt;You can already take advantage of this using your favourite cloud provider by picking a higher-tiered VM. You&amp;rsquo;ll still
be
paying the cloud tax, but it&amp;rsquo;s going to be cheaper than a Kubernetes cluster, and your stack will remain simple, fast
and portable.&lt;/p&gt;
&lt;p&gt;If you want to go even further, you can buy 2+ physical servers, find a suitable location to host them,
and take full advantage of modern hardware. At a certain scale, this will be much cheaper than &lt;em&gt;the cloud&lt;/em&gt;,
even if you need to hire somebody to manage, maintain and replace them. Physical servers aren&amp;rsquo;t scary, and you&amp;rsquo;ll need
knowledgeable platform people working for you either way, so why not cut out the complexity and expense of the cloud?&lt;sup id="fnref:4"&gt;&lt;a class="footnote-ref" href="#fn:4"&gt;4&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;Kubernetes is a perfectly good option to go with, but only at the right level of organizational size and maturity.
Unless you&amp;rsquo;re at that level, you really don&amp;rsquo;t need to worry about using it.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;right after they&amp;rsquo;re done implementing &amp;ldquo;AI&amp;rdquo; and LLM-s on a completely unsuitable use case.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;the only thing worse than managed Kubernetes is a poorly managed self-hosted one.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;turns out that it&amp;rsquo;s a fancy name for a reverse proxy. You know, like &lt;code&gt;nginx&lt;/code&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;there are benefits to using the cloud, but just like Kubernetes, cloud services have a narrow set of circumstances
where their use is appropriate.&amp;#160;&lt;a class="footnote-backref" href="#fnref:4"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>./techtipsy</author><pubDate>Tue, 01 Oct 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/10/01/kubernetes/</guid></item><item><title>2024.09.DisappearingMoment</title><link>https://newsletter.disappearingmoment.com/archive/202409disappearingmoment/</link><description>&lt;p&gt;“A device that permits people who haven’t anything to do to watch people who can’t do anything.” That was comedian Fred Allen's description of... television&lt;/p&gt;
&lt;p&gt;&lt;a href="https://youtu.be/SHx4xVhfS3w" target="_blank"&gt;Everything old is TikTok again&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;In my mid-twenties, Penn hired me to ghostwrite thank you letters to its donors. Someone at the President’s office signed them for her. Someone else mailed them to the donors. &lt;/p&gt;
&lt;p&gt;The donors employed people to open their mail and shred or recycle the letters. If the employee was new or rushed, the employee might have given the letter to the donor's accountant. Who would have shredded or recycled the letter.&lt;/p&gt;
&lt;p&gt;I was ChatGPT. The donors’ employees were ChatGPT. We were slower, cheaper, and burned less fuel. Technology!&lt;/p&gt;
&lt;p&gt;A couple of months ago, I read a post on an American Library Association mailing list that made me angry. The message to the list was typical &lt;a href="https://rationalwiki.org/wiki/Just_asking_questions" target="_blank"&gt;“just asking questions”&lt;/a&gt; bigotry. I responded before I thought better of it. &lt;/p&gt;
&lt;p&gt;I’m not embarrassed about what I wrote in my response. It was fine. It was what I would have wanted someone else to send to the list.&lt;/p&gt;
&lt;p&gt;I’m embarrassed by my offline reaction. I got sucked in for a few days. I gave the jackass space in my brain. I drafted a response to their response. Which I had the good sense not to send. &lt;/p&gt;
&lt;p&gt;That isn’t the point. The point is, I know better than to let dullards distract me from what matters. I learned it when I wrote letters that meant nothing to anyone. I learned it on Usenet. On social media.&lt;/p&gt;
&lt;p&gt;Here’s to never needing to learn it again.&lt;/p&gt;
&lt;p&gt;Welcome to September 2024’s Disappearing Moment, an inventory of my experiences. I hope you enjoy it.&lt;/p&gt;
&lt;h2&gt;Podcasts&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://lnns.co/XsW2FPrx_oV" target="_blank"&gt;Library Social Work&lt;/a&gt; (I Liked It): &lt;a href="https://orcid.org/0000-0002-2032-6425" target="_blank"&gt;Sarah Johnson&lt;/a&gt; researches library-based social workers. They're some of the coolest people on the planet. Her conversations with them make my day.&lt;/p&gt;
&lt;h2&gt;Nerdy Software&lt;/h2&gt;
&lt;p&gt;I use &lt;a href="https://gohugo.io/" target="_blank"&gt;Hugo&lt;/a&gt;, a &lt;a href="https://en.wikipedia.org/wiki/Content_management_system" target="_blank"&gt;Content Management System&lt;/a&gt;, to organize the &lt;a href="https://disappearingmoment.com/" target="_blank"&gt;Disappearing Moment website&lt;/a&gt;. It’s fun, powerful, elegant, open source, and has great &lt;a href="https://discourse.gohugo.io/" target="_blank"&gt;community support&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Free Font&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://luciole-vision.com/luciole-en.html" target="_blank"&gt;Luciole&lt;/a&gt; tries "to provide the best possible reading experience for the visually impaired". I find its design more subtle than other fonts with similar aims.&lt;/p&gt;
&lt;h2&gt;Bougie Products&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.injinji.com/" target="_blank"&gt;Injinji&lt;/a&gt; toe socks prevent blisters when I run. I also wear them with Luna sandals in cold weather or long runs, including a marathon.&lt;/p&gt;
&lt;h2&gt;Personal Finance and Investing&lt;/h2&gt;
&lt;p&gt;Like me, you may have a high mortgage rate. Use a &lt;a href="https://www.mortgageretirementprofessor.com/CalculatorArticles/Refinance-Calculator.html" target="_blank"&gt;Mortgage Professor refinance calculator&lt;/a&gt; to see if refinancing is worth it.&lt;/p&gt;
&lt;h2&gt;Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Craig Calcaterra, &lt;em&gt;&lt;a href="https://www.arcadiapublishing.com/products/9781953368232" target="_blank"&gt;Rethinking Fandom&lt;/a&gt;&lt;/em&gt; (2022) (I Liked It): My least favorite kind of favorite book. His message is as accurate and essential as it is sad and cynical. &lt;/li&gt;
&lt;li&gt;Merve Emre, &lt;em&gt;&lt;a href="https://www.merveemre.com/paraliterary" target="_blank"&gt;The Personality Brokers&lt;/a&gt;&lt;/em&gt; (2018) (I Loved It): My favorite kind of favorite book. Everyone is a flawed, credulous, &lt;a href="https://store.dftba.com/products/beautiful-nerd-shirt" target="_blank"&gt;beautiful nerd&lt;/a&gt;, including the author.&lt;/li&gt;
&lt;li&gt;Saul Justin Newman, “&lt;a href="https://www.biorxiv.org/content/10.1101/704080v3.full" target="_blank"&gt;Supercentenarian and remarkable age records exhibit patterns indicative of clerical errors and pension fraud&lt;/a&gt;” (I Liked It): So much for my Extra Virtuous Olive Oil. See also, “&lt;a href="https://theconversation.com/the-data-on-extreme-human-ageing-is-rotten-from-the-inside-out-ig-nobel-winner-saul-justin-newman-239023" target="_blank"&gt;The data on extreme human ageing is rotten from the inside out&lt;/a&gt;.”&lt;/li&gt;
&lt;li&gt;Matthew Ström “&lt;a href="https://matthewstrom.com/writing/copying/" target="_blank"&gt;Copying&lt;/a&gt;“ (I Loved It): I don't want to be original. I wouldn’t be cool if I could. What's satisfying is identifying cool people and adapting their ideas.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Too Much Information&lt;/h2&gt;
&lt;p&gt;Want to know more about me? I have links to my scores, types, etc. on &lt;a href="https://disappearingmoment.com/about/" target="_blank"&gt;my About page&lt;/a&gt;. Care to know thyself? Follow the links and reveal your soul. &lt;/p&gt;
&lt;p&gt;It's more effective if you do it on work time.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.animalinyou.com/" target="_blank"&gt;Animal Personality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Big_Five_personality_traits" target="_blank"&gt;Big Five&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gallup.com/cliftonstrengths/en/home.aspx" target="_blank"&gt;CliftonStrenths&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.discprofile.com/" target="_blank"&gt;DiSC Style&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.enneagraminstitute.com/type-descriptions/" target="_blank"&gt;Enneagram Number&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/F-scale_%28personality_test%29" target="_blank"&gt;F Scale&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://johnnythunderstheheartbreakers.bandcamp.com/track/blank-generation-version-2" target="_blank"&gt;Generation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.thinkherrmann.com/hbdi" target="_blank"&gt;Herrmann Brain Dominance Instrument Score&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.idiinventory.com/" target="_blank"&gt;Intercultural Development Inventory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Jungian_Type_Index" target="_blank"&gt;Jungian Type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.keirsey.com/" target="_blank"&gt;Keirsey Temperament&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mrg.com/assessments/leadership/" target="_blank"&gt;Leadership Effectiveness Analysis&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.themyersbriggs.com/en-US/Products-and-Services/Myers-Briggs" target="_blank"&gt;Myers-Briggs Type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Oxford_Capacity_Analysis" target="_blank"&gt;Novis Mental Ability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://assessment.oadllc.com/" target="_blank"&gt;Organization Analysis and Design Traits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://processcommunicationmodel.com/" target="_blank"&gt;Process Communication Model Personality Type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC269997/" target="_blank"&gt;Quality of Life&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://r-pas.org/" target="_blank"&gt;Rorschach Performance Assessment System Scores&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ncbi.nlm.nih.gov/pmc/articles/PMC8169328/" target="_blank"&gt;Swedish Universities Scales of Personality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.truecolorsintl.com/about" target="_blank"&gt;True Color&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://programs.clearerthinking.org/personality.html" target="_blank"&gt;Ultimate Personality&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://psytech.com/Assessments/ValuesAndMotivesInventory" target="_blank"&gt;Values and Motives Inventory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://winslowsolutions.com/profile-marketing-page/" target="_blank"&gt;Winslow Profile Traits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nga.gov/collection/art-object-page.3446.html" target="_blank"&gt;X&lt;/a&gt; (Following and Followers)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://osf.io/tn2vg/download" target="_blank"&gt;Yale-Brown Obsessive Compulsive Score&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Zodiac Sign&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Thank you for spending a few moments with me. I appreciate you and look forward to corresponding again next month.&lt;/p&gt;
&lt;p&gt;Brett&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want to discuss any of the topics in this newsletter or anything else with other Disappearing Moment readers? Please sign up for &lt;a href="https://august.disappearingmoment.com/" target="_blank"&gt;Perpetual August&lt;/a&gt;. I think it might be fun.&lt;/em&gt;&lt;/p&gt;</description><author>Disappearing Moment</author><pubDate>Tue, 01 Oct 2024 04:46:52 GMT</pubDate><guid isPermaLink="true">https://newsletter.disappearingmoment.com/archive/202409disappearingmoment/</guid></item><item><title>Linux Assembly Part 1: Syscalls</title><link>https://cookie.engineer/weblog/articles/linux-assembly-part-1-syscalls.html</link><description>Learn Linux assembly to execute syscalls.</description><author>Cookie Engineer's Web Log</author><pubDate>Tue, 01 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://cookie.engineer/weblog/articles/linux-assembly-part-1-syscalls.html</guid></item><item><title>AI-Generated Images Discourage Me From Reading Your Blog</title><link>https://nelson.cloud/ai-generated-images-discourage-me-from-reading-your-blog/?ref=rss</link><description>If you&amp;rsquo;re willing to use AI-generated images, how do I know the text isn&amp;rsquo;t AI-generated?</description><author>Nelson Figueroa</author><pubDate>Tue, 01 Oct 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/ai-generated-images-discourage-me-from-reading-your-blog/?ref=rss</guid></item><item><title>October 2024 - Bookmarks</title><link>https://domenicoluciani.com/2024/10/01/bookmarks.html</link><description>Bookmarks for October 2024: 1 link - Your Brain Changes Based on What You Did Two Weeks Ago -....</description><author>Domenico Luciani</author><pubDate>Tue, 01 Oct 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://domenicoluciani.com/2024/10/01/bookmarks.html</guid></item><item><title>How Chain of Thought Prompting Boosts LLM Performance</title><link>https://khromov.se/how-chain-of-thought-prompting-boosts-llm-performance/</link><description>&lt;p&gt;If you&amp;#8217;ve been following AI news lately, you might have heard about the &amp;#8220;strawberry test&amp;#8221;. It&amp;#8217;s a simple question that stumps many AI models: How many R&amp;#8217;s are in the word &amp;#8220;strawberry&amp;#8221;? This seemingly easy task highlights some interesting limitations in large language models (LLMs). Prefer a video version of this blog post? Watch below: Why is counting so hard for LLMs? To understand why this simple task is challenging for AI, we need to look at how Large Language Models work. LLMs don&amp;#8217;t read language the way humans do. Instead, they&amp;#8217;re essentially very sophisticated word prediction machines. When given [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://khromov.se/how-chain-of-thought-prompting-boosts-llm-performance/"&gt;How Chain of Thought Prompting Boosts LLM Performance&lt;/a&gt; appeared first on &lt;a href="https://khromov.se"&gt;Stanislav Khromov&lt;/a&gt;.&lt;/p&gt;</description><author>Stanislav Khromov</author><pubDate>Tue, 01 Oct 2024 00:28:26 GMT</pubDate><guid isPermaLink="true">https://khromov.se/how-chain-of-thought-prompting-boosts-llm-performance/</guid></item><item><title>Hugo Page Bundles</title><link>https://www.danielcorin.com/til/hugo/page-bundles/</link><description>Hugo Page Bundles</description><author>Thought Eddies</author><pubDate>Mon, 30 Sep 2024 21:42:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/hugo/page-bundles/</guid></item><item><title>Last mile is always the hardest</title><link>https://evilcookie.de/last-mile-is-always-the-hardest.html</link><description/><author>blog</author><pubDate>Mon, 30 Sep 2024 13:26:33 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/last-mile-is-always-the-hardest.html</guid></item><item><title>Fattura Elettronica v3.5</title><link>https://nicolaiarocci.com/fattura-elettronica-v3.5/</link><description>&lt;p&gt;I just released &lt;a href="https://www.nuget.org/packages/FatturaElettronica"&gt;FatturaElettronica .NET v3.5.0&lt;/a&gt;. This version adds multi-language support, all thanks to the excellent work done by &lt;a href="https://michaelmairegger.it"&gt;Michael Mairegger&lt;/a&gt;. We currently support Italian and German and are ready to accept contributions for other languages. The Fattura Elettronica open-source project allows for the validation and de/serialization of electronic invoices that adhere to the standard defined by the Italian Revenue Agency.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Mon, 30 Sep 2024 10:39:20 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/fattura-elettronica-v3.5/</guid></item><item><title>Turn Your Old Router Into a Highly Compatible Access Point</title><link>https://ivymike.dev/turn-your-old-router-into-a-highly-compatible-access-point.html</link><description>&lt;p&gt;&lt;a href="https://www.flickr.com/photos/jshappell/267527992"&gt;Photo credit&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Do you have any badly behaved IoT devices?  You know, the ones that can't seem to connect to your WiFi, or once they do connect, quickly fall off the network?  Or worse yet, the device might go comatose and need rebooting frequently?&lt;/p&gt;
&lt;p&gt;For example, I had a hell …&lt;/p&gt;</description><author>IvyMike.dev</author><pubDate>Mon, 30 Sep 2024 10:00:00 GMT</pubDate><guid isPermaLink="true">https://ivymike.dev/turn-your-old-router-into-a-highly-compatible-access-point.html</guid></item><item><title>To Broadcast or Not to Broadcast: A Nuanced Perspective</title><link>https://gavinhoward.com/2024/09/to-broadcast-or-not-to-broadcast-a-nuanced-perspective/</link><description>Adolfo Ochagavía wrote a great post on whether people should broadcast themselves. As someone who is infamous, I have some more advice.</description><author>Gavin D. Howard</author><pubDate>Mon, 30 Sep 2024 08:24:34 GMT</pubDate><guid isPermaLink="true">https://gavinhoward.com/2024/09/to-broadcast-or-not-to-broadcast-a-nuanced-perspective/</guid></item><item><title>New Graduate Advice</title><link>https://www.mgaudet.ca/blog/2024/9/29/new-graduate-advice</link><description>&lt;p class=""&gt;Every now and then a student or relatively junior person will email me asking for advice. I see responding to these sorts of things as a service I can do the community, much in the same way that I see running &lt;a href="https://mgaudet.github.io/CompilerJobs/" target="_blank"&gt;the Compiler Jobs page&lt;/a&gt;. &lt;/p&gt;&lt;p class=""&gt;Sometime during the pandemic I wrote the following… unfortunately, I have also some how lost track of the original recipient (if this is you, reach out! I’d love to hear what happened to you). Nevertheless, I append this to most advice I send out to new-grads as I think it has generally held up over time. &lt;/p&gt;&lt;p class=""&gt;It’s been sent out one-on-one enough that it’s time for me to just put a blog post version of it out. Gently edited here is my New Grad Advice: &lt;/p&gt;&lt;blockquote&gt;&lt;p class=""&gt; &lt;em&gt;A few things I'd suggest:&lt;/em&gt;&lt;/p&gt;&lt;p class=""&gt;&lt;em&gt;1. Cultivate your personal network, and I don't mean in the LinkedIn way: You will meet great colleagues and collaborators throughout your career: Make a concerted effort to keep in touch with them. Correspond with them, write them letters; when COVID is over and if you're in the same city, go out for coffee/beer/dinner/lunch as appropriate and time allows. This work will not pay dividends for years. However, by keeping in touch with people who respect and know you, and you in turn respect, you will know where interesting work is happening (and sometimes, where it's not!)&lt;/em&gt;&lt;/p&gt;&lt;p class=""&gt;&lt;em&gt;2. If you have the opportunity, as soon as you are able to, start mentoring: If you're an intern, but you've been there for 8 months, then mentor the interns who have been there for 1; if you're full time, make yourself a go-to person for interns.&lt;/em&gt;&lt;/p&gt;&lt;p class=""&gt;&lt;em&gt;There's two major reasons to do this: 1) you can demonstrate (and discover your own aptitude/interest in) leadership early; this is a skill that takes time to learn, but opportunities are few and far between. 2) You'd be surprised at what happens by paying attention to interns and junior employees: You're forced constantly to skill up your own understanding: You know best what you can teach. As well, you start to identify pain points that appear repeatedly (these often are organizational pain points that get forgotten after 2 months work): These can become projects that you can own that make everyone's work better.&lt;/em&gt;&lt;/p&gt;&lt;p class=""&gt;&lt;em&gt;3) Open Source work is good, but to turn it into something career building can be a prohibitive expense; but if you can find jobs working in open source, it is a bit of a career aid, as you're able to point directly at projects, commits and bugs you're particularly proud of.&lt;/em&gt;&lt;/p&gt;&lt;p class=""&gt;&lt;em&gt;This isn't to say don't contribute to open source projects in your spare time if you'd like: But a few small PRs contributed to a few big name projects isn't necessarily going to build your career. Instead, more sustained contribution can unlock mentorship opportunities which can in turn unlock career opportunities.&lt;/em&gt;&lt;/p&gt;&lt;p class=""&gt;&lt;em&gt;4) Find mentorship: Find the people you work with who are willing to take time to explain things, and nurture those relationships. Having someone at your work be invested in &lt;/em&gt;&lt;strong&gt;&lt;em&gt;you&lt;/em&gt;&lt;/strong&gt;&lt;em&gt; is important -- and ultimately the key to building a stronger career. You can't and won't do it all alone: so find the people who are in positions to help. Sometimes mentorship is a formal relationship; more often than not it's simply feedback you get from someone you respect, on a regular basis.&lt;/em&gt;&lt;/p&gt;&lt;p class=""&gt;&lt;em&gt;5) Be kind: Finding mentorship is easier if you're kind; if/when you have the opportunity to choose your own work (or. perhaps do some of it anyhow), choose problems that unblock others, lower the team's frustration level, and generally improve things. It's fun to build the cool new feature: It's better for you to fix the 1/100 crashing bug that's preventing everyone's builds from being green.&lt;/em&gt;&lt;/p&gt;&lt;p class=""&gt;&lt;em&gt;Hopefully this helps.&lt;/em&gt;&lt;/p&gt;&lt;/blockquote&gt;</description><author>Matthew Gaudet</author><pubDate>Mon, 30 Sep 2024 05:11:24 GMT</pubDate><guid isPermaLink="true">https://www.mgaudet.ca/blog/2024/9/29/new-graduate-advice</guid></item><item><title>Phrase Matching in Marginalia Search</title><link>https://www.marginalia.nu/log/a_111_phrase_matching/</link><description>&lt;p&gt;Marginalia Search now properly supports phrase matching. This not only permits a more robust implementation of quoted search queries, but also helps promote results where the search terms occur in the document exactly in the same order as they do in the query.&lt;/p&gt;
&lt;p&gt;This is a write-up about implementing this change. This is going to be a relatively long post, as it represents about 4 months of work.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m also happy and grateful to announce that the nlnet people reached out after the run of &lt;a href="../a_107_nlnext"&gt;the grant&lt;/a&gt; was over and asked me if I had more work in the pipe, and agreed to fund this change as well!&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Mon, 30 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_111_phrase_matching/</guid></item><item><title>Rust needs a web framework for lazy developers</title><link>https://ntietz.com/blog/rust-needs-a-web-framework-for-lazy-developers/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;I like to make silly things, and I also like to put in &lt;em&gt;minimal effort&lt;/em&gt; for those silly things.
I also like to make things in Rust, mostly for the web, and this is where we run into a problem.&lt;/p&gt;
&lt;p&gt;See, if I want to make something for the web, I could use Django but I don't want that.
I mean, Django is for building &lt;a href="https://engineering.fb.com/2023/09/07/culture/threads-inside-story-metas-newest-social-app/"&gt;serious businesses&lt;/a&gt;, not for building silly non-commercial things!
But using Rust, we have to do a &lt;em&gt;lot&lt;/em&gt; more work than if we build it with Django or friends.&lt;/p&gt;
&lt;p&gt;See, so far, there's no equivalent, and the Rust community leans heavily into the "wire it up yourself" approach.
As &lt;a href="https://www.arewewebyet.org/"&gt;Are We Web Yet?&lt;/a&gt; says, "[...] you generally have to wire everything up yourself. Expect to put in a little bit of extra set up work to get started."&lt;/p&gt;
&lt;p&gt;This undersells it, though.
It's more than a little bit of extra work to get started!
I know because I made a list of things to do to get started.&lt;/p&gt;
&lt;p&gt;Rust needs something that &lt;em&gt;does&lt;/em&gt; bundle this up for you, so that we can serve all web developers.
Having it would make it a lot easier to make the case to use Rust.
The benefits are there: you get wonderful type system, wonderful performance, and build times that give you back those coffee breaks you used to get while your code compiled.&lt;/p&gt;
&lt;h1 id="what-do-we-need"&gt;What do we need?&lt;/h1&gt;
&lt;p&gt;There is a big pile of stuff that nearly every web app needs, no matter if it's big or small.
Here's a rough list of what seems pretty necessary to me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Routing/handlers: this is pretty obvious, but we have to be able to get an incoming request to some handler for it. Additionally, this routing needs to handle path parameters, ideally with type information, and we'll give bonus points for query parameters, forms, etc.&lt;/li&gt;
&lt;li&gt;Templates: we'll need to generate, you know, HTML (and sometimes other content, like JSON or, if you're in the bad times still, XML). Usually I want these to have basic logic, like conditionals, match/switch, and loops.&lt;/li&gt;
&lt;li&gt;Static file serving: we'll need to serve some assets, like CSS files. This can be done separately, but having it as part of the same web server is extremely handy for both local development and for small-time deployments that won't handle much traffic.&lt;/li&gt;
&lt;li&gt;Logins: You almost always need some way to log in, since apps are usually multi-user or deployed on a public network. This is just annoying to wire up every time! It should be customizable and something you can opt out of, but it should be trivial to have logins from the start.&lt;/li&gt;
&lt;li&gt;Permissions: You also need this for systems that have multiple users, since people will have different data they're allowed to access or different roles in the system. Permissions can be complicated but you can make something relatively simple that follows the &lt;code&gt;check(user, object, action)&lt;/code&gt; pattern and get really far with it.&lt;/li&gt;
&lt;li&gt;Database interface: You're probably going to have to store data for your app, so you want a way to do that. Something that's ORM-like is often nice, but something light is fine. Whatever you do here isn't the &lt;em&gt;only&lt;/em&gt; way to interact with the database, but it'll be used for things like logins, permissions, and admin tools, so it's going to be a fundamental piece.&lt;/li&gt;
&lt;li&gt;Admin tooling: This is &lt;em&gt;arguably&lt;/em&gt; a quality-of-life issue, not a necessity, except that every time you setup your application in a local environment or in production you're going to have to bootstrap it with at least one user or some data. And you'll have to do admin actions sometimes! So I think having this built-in for at least some of the common actions is a necessity for a seamless experience.&lt;/li&gt;
&lt;li&gt;WebSockets: I use WebSockets in a lot of my projects. They just let you do really fun things with pushing data out to connected users in a more real-time fashion!&lt;/li&gt;
&lt;li&gt;Hot reloading: This is a &lt;em&gt;huge&lt;/em&gt; one for developer experience, because you want to have the ability to see changes really quickly. When code or a template change, you need to see that reflected as soon as humanly possible (or as soon as the Rust compiler allows).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then we have a pile of things that are quality-of-life improvements, and I think are necessary for long-term projects but might not be as necessary upfront, so users are less annoyed at implementing it themselves because the cost is spread out.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Background tasks: There needs to be a story for these! You're going to have features that have to happen on a schedule, and having a consistent way to do that is a big benefit and makes development easier.&lt;/li&gt;
&lt;li&gt;Monitoring/observability: Only the smallest, least-critical systems should skip this. It's really important to have and it will make your life so much easier when you have it in that moment that you desperately need it.&lt;/li&gt;
&lt;li&gt;Caching: There are a lot of ways to do this, and all of them make things more complicated and &lt;em&gt;maybe&lt;/em&gt; faster? So this is nice to have a story for, but users can also handle it themselves.&lt;/li&gt;
&lt;li&gt;Emails and other notifications: It's neat to be able to have password resets and things built-in, and this is probably a necessity if you're going to have logins, so you can have password resets. But other than that feature, it feels like it won't get used &lt;em&gt;that&lt;/em&gt; much and isn't a big deal to add in when you need it.&lt;/li&gt;
&lt;li&gt;Deployment tooling: Some consistent way to deploy &lt;em&gt;somewhere&lt;/em&gt; is really nice, even if it's just an autogenerated Dockerfile that you can use with a host of choice.&lt;/li&gt;
&lt;li&gt;CSS/JS bundling: In the time it is, we use JS and CSS everywhere, so you probably want a web tool to be aware of them so they can be included seamlessly. But does it really have to be integrated in? Probably not...&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So those are the things I'd target in a framework if I were building one!
I might be doing that...&lt;/p&gt;
&lt;h1 id="the-existing-ecosystem"&gt;The existing ecosystem&lt;/h1&gt;
&lt;p&gt;There's quite a bit out there already for building web things in Rust.
None of them quite hit what I want, which is intentional on their part: none of them &lt;em&gt;aspire&lt;/em&gt; to be what I'm looking for here.
I love what exists, and I think we're sorely missing what I want here (I don't think I'm alone).&lt;/p&gt;
&lt;h2 id="web-frameworks"&gt;Web frameworks&lt;/h2&gt;
&lt;p&gt;There are really two main groups of web frameworks/libraries right now: the minimalist ones, and the single-page app ones.&lt;/p&gt;
&lt;p&gt;The minimalist ones are reminiscent of &lt;a href="https://flask.palletsprojects.com/en/3.0.x/"&gt;Flask&lt;/a&gt;, Sinatra, and other small web frameworks.
These include the excellent &lt;a href="https://actix.rs/"&gt;actix-web&lt;/a&gt; and &lt;a href="https://docs.rs/axum/latest/axum/"&gt;axum&lt;/a&gt;, as well as myriad &lt;a href="https://www.arewewebyet.org/topics/frameworks/"&gt;others&lt;/a&gt;.
There are so many of these, and they all bring a nice flavor to web development by leveraging Rust's type system!
But they don't give you much besides handlers; none of the extra functionality we want in a full for-lazy-developers framework.&lt;/p&gt;
&lt;p&gt;Then there are the single-page app frameworks.
These fill a niche where you can build things with Rust on the backend and frontend, using WebAssembly for the frontend rendering.
These tend to be less mature, but good examples include &lt;a href="https://dioxuslabs.com/learn/0.5/"&gt;Dioxus&lt;/a&gt;, &lt;a href="https://crates.io/crates/leptos"&gt;Leptos&lt;/a&gt;, and &lt;a href="https://yew.rs/"&gt;Yew&lt;/a&gt;.
I used Yew to build a &lt;a href="https://ntietz.com/blog/digital-vigil-for-tdor/"&gt;digital vigil&lt;/a&gt; last year, and it was enjoyable but I'm not sure I'd want to do it in a "real" production setting.&lt;/p&gt;
&lt;p&gt;Each of these is excellent for what it is—but what it is requires a lot of wiring up still.
Most of my projects would work well with the minimalist frameworks, but those require so much wiring up!
So it ends up being a chore to set that up each time that I want to do something.&lt;/p&gt;
&lt;h2 id="piles-of-libraries"&gt;Piles of libraries!&lt;/h2&gt;
&lt;p&gt;The rest of the ecosystem is piles of libraries.
There are lots of template libraries!
There are some libraries for logins, and for permissions.
There are WebSocket libraries!&lt;/p&gt;
&lt;p&gt;Often you'll find some projects and examples which integrate a couple of the things you're using, but you won't find something that integrates &lt;em&gt;all&lt;/em&gt; the pieces you're using.
I've run into some of the examples being out of date, which is to be expected in a fast-moving ecosystem.&lt;/p&gt;
&lt;p&gt;The pile of libraries leaves a lot of friction, though.
It makes getting started, the "just wiring it up" part, very difficult and often an exercise in researching how things work, to understand them in depth enough to &lt;em&gt;do&lt;/em&gt; the integration.&lt;/p&gt;
&lt;h2 id="what-i-ve-done-before"&gt;What I've done before&lt;/h2&gt;
&lt;p&gt;The way I've handled this before is basically to pick a base framework (typically actix-web or axum) and then search out all the pieces I want on top of it.
Then I'd wire them up, either all at the beginning or as I need them.&lt;/p&gt;
&lt;p&gt;There are starter templates that could help me avoid some of this pain.
They can definitely help you skip some of the initial pain, but you still get all the maintenance burden.
You have to make sure your libraries stay up to date, even when there are breaking changes.
And you will drift from the template, so it's not really feasible to merge changes to it into your project.&lt;/p&gt;
&lt;p&gt;For the projects I'm working on, this means that instead of keeping one framework up to date, I have to keep &lt;code&gt;n&lt;/code&gt; bespoke frameworks up to date across all my projects!&lt;/p&gt;
&lt;p&gt;Eep!&lt;/p&gt;
&lt;p&gt;I'd much rather have a single web framework that handles it all, with clean upgrade instructions between versions.
There will be breaking changes sometimes, but this way they can be documented instead of coming about due to changes in the interactions between two components which don't even know they're going to be integrated together.&lt;/p&gt;
&lt;h1 id="imagining-the-future-i-want"&gt;Imagining the future I want&lt;/h1&gt;
&lt;p&gt;In an ideal world, there would be a framework for Rust that gives me all the features I listed above.
And it would also come with excellent documentation, changelogs, thoughtful versioning and handling of breaking changes, and maybe even a great community.
All the things I love about Django, could we have those for a Rust web framework so that we can reap the benefits of Rust without having to go needlessly slowly?&lt;/p&gt;
&lt;p&gt;This doesn't exist right now, and I'm not sure if anyone else is working on it.
All paths seem to lead me toward "whoops I guess I'm building a web framework."
I hope someone else builds one, too, so we can have multiple options.&lt;/p&gt;
&lt;p&gt;To be honest, "web framework" sounds way too grandiose for what I'm doing, which is simply wiring things together in an opinionated way, using (mostly) existing building blocks&lt;sup class="footnote-reference" id="fr-a-few-of-my-own-1"&gt;&lt;a href="https://ntietz.com/blog/rust-needs-a-web-framework-for-lazy-developers/#fn-a-few-of-my-own"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
Instead of calling it a framework, I'm thinking of it as a &lt;em&gt;web toolkit&lt;/em&gt;: a bundle of tools tastefully chosen and arranged to make the artisan highly effective.&lt;/p&gt;
&lt;p&gt;My toolkit is called &lt;strong&gt;nicole's web toolkit&lt;/strong&gt;, or &lt;strong&gt;&lt;code&gt;newt&lt;/code&gt;&lt;/strong&gt;.
It's available in a &lt;a href="https://git.sr.ht/~ntietz/newt"&gt;public repository&lt;/a&gt;, but it's really not usable (the latest changes aren't even pushed yet).
It's not even usable for &lt;em&gt;me&lt;/em&gt; yet—this isn't a launch post, more shipping my design doc (and hoping someone will do my work for me so I don't &lt;em&gt;have&lt;/em&gt; to finish &lt;code&gt;newt&lt;/code&gt; :D).&lt;/p&gt;
&lt;p&gt;The goal for &lt;code&gt;newt&lt;/code&gt; is to be able to create a new small web app and start on the actual project in minutes instead of days, bypassing the entire process of wiring things up.
I think the list of must-haves and quality-of-life features above will be a start, but by no means everything we need.
I'm not ready to accept contributions, but I hope to be there at some point.&lt;/p&gt;
&lt;p&gt;I think that Rust really needs this, and the whole ecosystem will benefit from it.
A healthy ecosystem will have multiple such toolkits, and I hope to see others develop as well.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;p&gt;If you want to follow along with mine, though, feel free to subscribe to my RSS feed or newsletter, or follow me on Mastodon.
I'll try to let people know in all those places when the toolkit is ready for people to try out.
Or I'll do a post-mortem on it, if it ends up that I don't get far with it!
Either way, this will be fun.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-a-few-of-my-own"&gt;
&lt;p&gt;I do plan to build a few pieces from scratch for this, as the need arises.
Some things will be easier that way, or fit more cohesively.
Can't I have a little greenfield, as a treat? &lt;a href="https://ntietz.com/blog/rust-needs-a-web-framework-for-lazy-developers/#fr-a-few-of-my-own-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 30 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/rust-needs-a-web-framework-for-lazy-developers/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Platforms Engineering</title><link>https://justingarrison.com/blog/2024-09-30-platforms-engineering/</link><description>The idea that you can build a single platform is making it worse</description><author>Justin Garrison's Homepage</author><pubDate>Mon, 30 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://justingarrison.com/blog/2024-09-30-platforms-engineering/</guid></item><item><title>SMTP Downgrade Attacks and MTA-STS</title><link>https://alexsci.com/blog/smtp-downgrade-attacks-and-mta-sts/</link><description>Running a bunch of mail servers to audit email security</description><author>Built on Shards of Silicon: Robert Alexander's Tech Blog</author><pubDate>Mon, 30 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://alexsci.com/blog/smtp-downgrade-attacks-and-mta-sts/</guid></item><item><title>Mexico City: my travel recommendations.</title><link>https://danielsada.tech/blog/mexico-city-recommendations/</link><description>I was born and raised in Mexico City, and I often get the question of what are my favorite places or plans to visit in Mexico City I enjoy that are compatible with foreigners and shine a light on some of the things Mexico has to offer.
Recommended Day 1 - Zocalo and Bellas Artes: Get to Bellas Artes (palace of fine arts). Go in, see the nice paintings, then when you head outside, to the left, you don&amp;rsquo;t want to miss the &amp;ldquo;Sanborns de los Azulejos&amp;rdquo;, it is beautifully decorated, a store and a restaurant.</description><author>Daniel Sada Caraveo | Developer Productivity &amp;amp; Culture</author><pubDate>Mon, 30 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://danielsada.tech/blog/mexico-city-recommendations/</guid></item><item><title>Cost and performance optimization of Amazon Athena through data partitioning</title><link>https://manuel.kiessling.net/2024/09/30/cost-and-performance-optimization-of-amazon-athena-through-data-partitioning/</link><description>Introduction &amp;amp; Context At JOBOO we work in a highly data-driven manner, with all of our application platforms continuously streaming so-called event data records to our data warehouse.
Each of these records encapsulates a relevant event from the areas
 Business (&amp;ldquo;a new user signed up&amp;rdquo;, &amp;ldquo;a user has taken out a subscription&amp;rdquo;) Application (&amp;ldquo;an email was sent to a user&amp;rdquo;, &amp;ldquo;an error occurred&amp;rdquo;) Web request (&amp;ldquo;Page X was requested with these and those parameters&amp;rdquo;) and Conversion tracking (&amp;ldquo;user has reached a campaign goal&amp;rdquo;).</description><author>Home on The Log Book of Manuel Kießling</author><pubDate>Mon, 30 Sep 2024 01:00:01 GMT</pubDate><guid isPermaLink="true">https://manuel.kiessling.net/2024/09/30/cost-and-performance-optimization-of-amazon-athena-through-data-partitioning/</guid></item><item><title>Kosten- und Performance-Optimierung von Amazon Athena durch Daten-Partitionierung</title><link>https://manuel.kiessling.net/2024/09/30/kosten-und-performance-optimierung-von-amazon-athena-durch-daten-partitionierung/</link><description>Einführung &amp;amp; Kontext Bei JOBOO arbeiten wir stark datengetrieben, indem alle unsere Anwendungsplattformen sogenannte Event-Daten kontinuierlich an unser Data Warehouse streamen.
Jeder dieser Event-Datensätze kapselt dabei ein relevantes Ereignis aus den Bereichen
 Geschäftsvorfälle (&amp;ldquo;ein User hat sich registriert&amp;rdquo;, &amp;ldquo;ein User hat ein Abonnement abgeschlossen&amp;rdquo;) Anwendung (&amp;ldquo;einem User wurde eine E-Mail zugestellt&amp;rdquo;, &amp;ldquo;ein Fehler ist aufgetreten&amp;rdquo;) Webaufruf (&amp;ldquo;Seite X wurde mit diesen und jenen Parametern aufgerufen&amp;rdquo;) und Conversion-Tracking (&amp;ldquo;User hat ein Kampagnenziel erreicht&amp;rdquo;).</description><author>Home on The Log Book of Manuel Kießling</author><pubDate>Mon, 30 Sep 2024 01:00:01 GMT</pubDate><guid isPermaLink="true">https://manuel.kiessling.net/2024/09/30/kosten-und-performance-optimierung-von-amazon-athena-durch-daten-partitionierung/</guid></item><item><title>Prompt Template for Word Definitions</title><link>https://www.danielcorin.com/til/prompting/word-definitions/</link><description>Prompt Template for Word Definitions</description><author>Thought Eddies</author><pubDate>Sun, 29 Sep 2024 23:20:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/prompting/word-definitions/</guid></item><item><title>Clickhouse for Embedded Analytics: First Impressions and Unexpected Challenges</title><link>https://jorin.me/clickhouse-for-embedded-analytics-first-impressions-and-unexpected-challenges/</link><description>I chose Clickhouse for an embedded analytics project. Here are my first impressions and some surprises I encountered along the way.</description><author>jorin.me</author><pubDate>Sun, 29 Sep 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://jorin.me/clickhouse-for-embedded-analytics-first-impressions-and-unexpected-challenges/</guid></item><item><title>Build a serverless ACID database with this one neat trick (atomic PutIfAbsent)</title><link>http://notes.eatonphil.com/2024-09-29-build-a-serverless-acid-database-with-this-one-neat-trick.html</link><description>&lt;p&gt;Delta Lake is an open protocol for serverless ACID databases. Due to
its simplicity, scalability, and the number of open-source
implementations, it's quickly becoming the DuckDB of serverless
transactional databases for analytics workloads. Iceberg is a
contender too, and is similar in many ways. But since Delta Lake is
simpler (simple != better) that's where we'll focus in this post.&lt;/p&gt;
&lt;p&gt;Delta Lake has one of the most accessible database papers I've read
(&lt;a href="https://www.vldb.org/pvldb/vol13/p3411-armbrust.pdf"&gt;link&lt;/a&gt;). It's
kind of like the
&lt;a href="https://github.com/xoreaxeaxeax/movfuscator"&gt;movfuscator&lt;/a&gt; of
databases.&lt;/p&gt;
&lt;p&gt;Thanks to its simplicity, in this post we'll implement a Delta
Lake-inspired serverless ACID database in 500 lines of Go code with
zero dependencies. It will support creating tables, inserting rows
into a table, and scanning all rows in a table. All while allowing
concurrent readers and writers and achieving &lt;a href="https://jepsen.io/consistency"&gt;snapshot
isolation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are other critical parts of Delta Lake we'll ignore: updating
rows, deleting rows, checkpointing the transaction metadata log,
compaction, and probably much more I'm not aware of. We must start
somewhere.&lt;/p&gt;
&lt;p&gt;All code for this post is &lt;a href="https://github.com/eatonphil/otf"&gt;available on GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="delta-lake-basics"&gt;Delta Lake basics&lt;/h3&gt;&lt;p&gt;Delta Lake writes immutable data files to blob storage. It stores the
names of new data files for a transaction in a metadata file. It
handles concurrency (i.e. achieves snapshot isolation) with an atomic
PutIfAbsent operation on the metadata file for the transaction.&lt;/p&gt;
&lt;p&gt;This method of concurrency control works because the metadata files
follow a naming scheme that includes the transaction id in the file
name. When a new transaction starts, it finds all existing metadata
files and picks its own transaction id by adding 1 to the largest
transaction id it sees.&lt;/p&gt;
&lt;p&gt;When a transaction goes to commit, writing the metadata file will
fail if another transaction has already picked the same transaction
id.&lt;/p&gt;
&lt;p&gt;If a transaction does no writes and creates no tables, the transaction
does not attempt to write any metadata file. Snapshot isolation!&lt;/p&gt;
&lt;p&gt;Let's dig into the implementation.&lt;/p&gt;
&lt;h3 id="boilerplate"&gt;Boilerplate&lt;/h3&gt;&lt;p&gt;Let's give ourselves some nice assertion methods, a debug method, and
a uuid generator. In &lt;code&gt;main.go&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;encoding/json&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;fmt&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;io&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;path&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;slices&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;strings&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;comparable&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%s '%v' != '%v'&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DEBUG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;slices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;--debug&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;DEBUG&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[DEBUG]&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// https://datatracker.ietf.org/doc/html/rfc4122#section-4.4&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;/dev/random&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not open /dev/random: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not read 16 bytes from /dev/random: %s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected 16 bytes from /dev/random&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Set bit 6 to 0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^(&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Set bit 7 to 1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Set version&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^(&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^(&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;^(&lt;/span&gt;&lt;span class="nb"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;%x-%x-%x-%x-%x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Is that uuid method correct? Hopefully. Efficient? No. But it's
preferable to avoid dependencies in pedagogical projects.&lt;/p&gt;
&lt;p&gt;Moving on.&lt;/p&gt;
&lt;h3 id="blob-storage-requirements"&gt;Blob storage requirements&lt;/h3&gt;&lt;p&gt;As mentioned above, the basic requirement is that we support
atomically writing some bytes to a location if the location doesn't
already exist.&lt;/p&gt;
&lt;p&gt;On top of that we also need the ability to list locations by prefix,
and the ability to read the bytes at some location.&lt;/p&gt;
&lt;p class="note"&gt;
  We'll diverge from Delta Lake in how we name files on disk. For one,
  we'll keep all files in the same directory with a fixed prefix for
  metadata and another table name prefix for each data file. This
  simplifies the implementation of &lt;code&gt;listPrefix&lt;/code&gt; a bit.
  &lt;br /&gt;
  &lt;br /&gt;
  However, this also diverges from Delta Lake in that transactions
  will represent all tables. In Delta Lake that is not so. Delta Lake
  has a per-table transaction log. Only transactions that read and
  write the same table in Delta Lake achieve snapshot isolation.
&lt;/p&gt;&lt;p&gt;So let's set up an interface to describe these requirements:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;objectStorage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;interface&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Must be atomic.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;putIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;listPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And this is literally all we need to get ACID transactions. That's crazy!&lt;/p&gt;
&lt;h4 id="atomic-put-and-cloud-blob-storage"&gt;Atomic Put and cloud blob storage&lt;/h4&gt;&lt;p&gt;We could implement the atomic &lt;code&gt;putIfAbsent&lt;/code&gt; part of this interface in
2024 using &lt;a href="https://aws.amazon.com/about-aws/whats-new/2024/08/amazon-s3-conditional-writes/"&gt;conditional
writes&lt;/a&gt;
on S3. Or we could implement this interface with the &lt;code&gt;If-None-Match&lt;/code&gt;
&lt;a href="https://learn.microsoft.com/en-us/rest/api/storageservices/specifying-conditional-headers-for-blob-service-operations"&gt;header&lt;/a&gt;
on Azure Cloud Storage. Or we could implement this interface with the
&lt;code&gt;x-goog-if-generation-match&lt;/code&gt;
&lt;a href="https://cloud.google.com/storage/docs/xml-api/put-object"&gt;header&lt;/a&gt; on
Google Cloud Storage.&lt;/p&gt;
&lt;p&gt;Indeed a good exercise for the reader would be to implement this
interface for other blob storage providers and see your serverless
cloud database in action!&lt;/p&gt;
&lt;p&gt;But the simplest method of all is to implement it on the filesystem,
which is what we'll do next.&lt;/p&gt;
&lt;h3 id="a-filesystem-blob-store"&gt;A filesystem blob store&lt;/h3&gt;&lt;p&gt;If we had a server we could implement atomic &lt;code&gt;putIfAbsent&lt;/code&gt; with a
mutex. But we're serverless baby. Thankfully, POSIX &lt;a href="https://rcrowley.org/2010/01/06/things-unix-can-do-atomically.html"&gt;supports atomic
link&lt;/a&gt;
which will fail if the new name is already a file.&lt;/p&gt;
&lt;p&gt;So we'll just create a temporary file and write out all
bytes. Finally, we link the temporary file to the permanent name we
intended. For cleanliness (not correctness), if there is an error at
any point, we'll remove the temporary file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newFileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;putIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_WRONLY&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_CREATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mo"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;bufSize&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;toWrite&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;bufSize&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nx"&gt;toWrite&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not remove&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;written&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not remove&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not remove&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpfilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;removeErr&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not remove&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p class="note"&gt;
  &lt;a href="https://news.ycombinator.com/item?id=41702593"&gt;yencabulator&lt;/a&gt;
  on HN pointed out that an earlier version of this post had a buggy
  implementation of &lt;code&gt;putIfAbsent&lt;/code&gt; (that attempted to manage
  atomicity solely via &lt;code&gt;O_EXCL | O_CREAT&lt;/code&gt;) would leave
  around potentially bad metadata files if the &lt;code&gt;os.Remove&lt;/code&gt;
  call ever failed.
  &lt;br /&gt;
  &lt;br /&gt;
  The &lt;code&gt;link&lt;/code&gt; approach works around that because the file is
  already fully and correctly written by the time we do the link.
&lt;/p&gt;&lt;p&gt;&lt;code&gt;listPrefix&lt;/code&gt; and &lt;code&gt;read&lt;/code&gt; are minimal wrappers around filesystem APIs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;listPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Readdirnames&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EOF&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;fileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;basedir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ReadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It is worth talking a bit about reading a directory though. Go doesn't
provide a nice iterator API for us and I didn't want to implement this
as callbacks with
&lt;a href="https://pkg.go.dev/path/filepath#WalkDir"&gt;&lt;code&gt;path/filepath.WalkDir&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We could use &lt;a href="https://pkg.go.dev/os#File.ReadDir"&gt;&lt;code&gt;os.File.ReadDir&lt;/code&gt;&lt;/a&gt;
but it allocates for all files in the directory. Sure, in a
pedagogical project we don't worry about millions of files. But the
&lt;code&gt;ReadDir&lt;/code&gt; API, the error cases in particular, also isn't much simpler
than &lt;a href="https://pkg.go.dev/os#File.Readdirnames"&gt;&lt;code&gt;Readdirnames&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p class="note"&gt;
  What's more, even though we iterated through batches of directory
  entries, and did prefix filtering before accumulating, we still could
  have considered returning an iterator here ourselves. It seems
  possible and likely that the number of data files grows quite large in
  a production system. But I was lazy.
&lt;/p&gt;&lt;p&gt;It would be nice if Go introduced an actual iterator API for
reading a directory. :)&lt;/p&gt;
&lt;h4 id="delta-lake-and-stale-reads"&gt;Delta Lake and stale reads&lt;/h4&gt;&lt;p&gt;In any case the ACID properties of Delta Lake (and Iceberg) don't
depend on being able to read up-to-date data.&lt;/p&gt;
&lt;p&gt;This is because concurrent (or stale) transactions that &lt;em&gt;write&lt;/em&gt; will
&lt;em&gt;fail on commit&lt;/em&gt;. And also because all files written (even metadata
files) are immutable.&lt;/p&gt;
&lt;p&gt;Since all data is immutable, we will always be able to read at least a
consistent snapshot of data. But we will never be able to get
SERIALIZABLE &lt;strong&gt;read-only&lt;/strong&gt; transactions. This is just how Delta Lake
and Iceberg work. And it is a &lt;a href="https://jepsen.io/consistency"&gt;similar&lt;/a&gt;
or better consistency level to what any major SQL database &lt;a href="https://github.com/ept/hermitage"&gt;gives you
by default&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You'll see what I mean later on when we implement transaction commits.&lt;/p&gt;
&lt;h3 id="transaction-boilerplate"&gt;Transaction boilerplate&lt;/h3&gt;&lt;p&gt;Now that we've got a blob storage abstraction and a filesystem
implementation of it, let's start sketching out what a client and what
a transaction looks like.&lt;/p&gt;
&lt;p&gt;In Delta Lake, a transaction consists of a list of actions. An action
might be to define a table's schema, or to add a data file, or to
remove a data file, etc. In this post we'll only implement the first
two actions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DataobjectAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadataAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// an enum, only one field will be non-nil&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;AddDataobject&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;DataobjectAction&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadataAction&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// TODO: Support object removal.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// DeleteDataobject *DataobjectAction&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These fields are all exported (i.e. capitalized, if you're not
familiar with Go) because we will be writing them to disk when the
transaction commits as the transaction's metadata.&lt;/p&gt;
&lt;p&gt;In fact &lt;code&gt;Action&lt;/code&gt;s and the transaction's id will be the only parts of
the transaction we write to disk. Everything else will be in-memory
state.&lt;/p&gt;
&lt;p&gt;For our convenience we will track in memory a history of all previous
actions, a mapping of table columns, and a mapping of unflushed data
by table.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Both are mapping table name to a list of actions on the table.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Mapping tables to column names.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Mapping table name to unflushed/in-memory rows. When rows&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// are flushed, the dataobject that contains them is added to&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// `tx.actions` above and `tx.unflushedDataPointer[table]` is&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// reset to `0`.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Only the current &lt;code&gt;transaction&lt;/code&gt; will ever have
&lt;code&gt;transaction.previousActions&lt;/code&gt; filled out. &lt;code&gt;transaction.tables&lt;/code&gt; will be
populated when the transaction starts by reading through
&lt;code&gt;transaction.previousActions&lt;/code&gt; for &lt;code&gt;ChangeMetadataAction&lt;/code&gt;s, and we will
also add onto it when we create a table in the current transaction.&lt;/p&gt;
&lt;p&gt;We will append to &lt;code&gt;transaction.Actions&lt;/code&gt; every time we write a new data
file and every time we create a new table.&lt;/p&gt;
&lt;p&gt;We will add rows to &lt;code&gt;transaction.unflushedData&lt;/code&gt; for a table until
&lt;code&gt;transaction.unflushedDataPointer&lt;/code&gt; for that table reaches
&lt;code&gt;DATAOBJECT_SIZE&lt;/code&gt; upon which time we will write that data to disk and
add a &lt;code&gt;DataobjectAction&lt;/code&gt; entry to &lt;code&gt;transaction.Actions&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="client-boilerplate"&gt;Client boilerplate&lt;/h3&gt;&lt;p&gt;A &lt;code&gt;client&lt;/code&gt; will consist of an &lt;code&gt;objectStorage&lt;/code&gt; implementation and a
possibly empty &lt;code&gt;*transaction&lt;/code&gt;. Empty meaning there is no current
transaction.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;objectStorage&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Current transaction, if any. Only one transaction per&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// client at a time. All reads and writes must be within a&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// transaction.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;objectStorage&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;errExistingTx&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Existing Transaction&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;No Transaction&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;errTableExists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Table Exists&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;errNoTable&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;No Such Table&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;h4 id="client-or-database?"&gt;Client or database?&lt;/h4&gt;&lt;p&gt;In a previous version of my code I named this &lt;code&gt;client&lt;/code&gt; struct
&lt;code&gt;database&lt;/code&gt;. But that's misleading. There is no central database. There
is just the client and the blob storage.&lt;/p&gt;
&lt;p&gt;Clients work with transactions directly and only when attempting to
commit does the blob storage abstraction let the client know if the
transaction succeeded or not.&lt;/p&gt;
&lt;h3 id="starting-a-transaction"&gt;Starting a transaction&lt;/h3&gt;&lt;p&gt;When we start a transaction, we will first read all existing
transactions from disk and accumulate the actions from each prior
transaction.&lt;/p&gt;
&lt;p&gt;We will interpret &lt;code&gt;ChangeMetadataAction&lt;/code&gt;s and materialize them into a
current view of all tables.&lt;/p&gt;
&lt;p&gt;And we will assign a transaction ID to this transaction to be 1
greater than the largest existing transaction ID we see.&lt;/p&gt;
&lt;p&gt;Again it doesn't matter if the &lt;code&gt;listPrefix&lt;/code&gt; call we use returns an
up-to-date list. Notably on blob storage there are few guarantees
about LIST operations recency. The Delta Lake paper mentions this too.&lt;/p&gt;
&lt;p&gt;Out-of-date transactions attempting to write will be caught when we go
to commit the transaction. Out-of-date transactions attempting only to
read will still read a consistent snapshot.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errExistingTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;logPrefix&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_log_&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;txLogFilenames&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;listPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;logPrefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;txLogFilename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;txLogFilenames&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;txLogFilename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oldTx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;transaction&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;oldTx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// Transaction metadata files are sorted&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// lexicographically so that the most recent&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// transaction (i.e. the one with the largest&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// transaction id) will be last and tx.Id will end up&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// 1 greater than the most recent transaction ID we&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// see on disk.&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oldTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;oldTx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddDataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;// Store the latest version of&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;// each table in memory for&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;// easy lookup.&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;mtd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadata&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;mtd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Columns&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unsupported action: %v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And we're set.&lt;/p&gt;
&lt;h3 id="creating-a-table"&gt;Creating a table&lt;/h3&gt;&lt;p&gt;When we create a table, we need to add a &lt;code&gt;ChangeMetadataAction&lt;/code&gt; to the
transactions &lt;code&gt;Actions&lt;/code&gt;. And we also want to add the table info to the
in-memory &lt;code&gt;transaction.tables&lt;/code&gt; field.&lt;/p&gt;
&lt;p&gt;We don't do any of this durably. The change here will be written to
disk on commit (if the transaction succeeds).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;createTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errTableExists&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Store it in the in-memory mapping.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// And also add it to the action history for future transactions.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;ChangeMetadataAction&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Easy peasy. Now for the fun part, writing data!&lt;/p&gt;
&lt;h3 id="writing-a-row"&gt;Writing a row&lt;/h3&gt;&lt;p&gt;This is the next area where we'll diverge from Delta Lake. For the
sake of zero dependencies we are going to store data in-memory as an
array of array of &lt;code&gt;any&lt;/code&gt;. And when we later write rows to disk we'll
write them as JSON. A real Delta Lake implementation would store data
in-memory in Apache Arrow format, and write to disk as Parquet.&lt;/p&gt;
&lt;p&gt;In line with Delta Lake though we will buffer data in memory until we
get 64K rows. When we get 64K rows for a particular table we will
flush all those rows to disk. (When we go to commit a transaction we
will flush any outstanding rows.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTable&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Try to find an unflushed/in-memory dataobject for this table&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flushRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now let's implement flushing.&lt;/p&gt;
&lt;h3 id="flushing-a-data-object"&gt;Flushing a data object&lt;/h3&gt;&lt;p&gt;Recall that data objects in Delta Lake (and Iceberg) are
immutable. Once we've got enough data to write a data object, we give
it a unique name, write it to disk, and add a &lt;code&gt;AddObjectAction&lt;/code&gt; to the
transaction's list of &lt;code&gt;Actions&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;Len&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;flushRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// First write out dataobject if there is anything to write out.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;exists&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;uuidv4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;Len&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_table_%s_%s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Then record the newly written data file.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;AddDataobject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;DataobjectAction&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Reset in-memory pointer.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's it for writing data! Let's now look at reading data.&lt;/p&gt;
&lt;h3 id="scanning-a-table"&gt;Scanning a table&lt;/h3&gt;&lt;p&gt;We're going to make scanning mildly more complicated than it needed to
be in pedagogical code because we'll have &lt;code&gt;client.scan()&lt;/code&gt; return an
iterator rather than an array with all rows.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;scanIterator&lt;/code&gt; will first read from in-memory (unflushed)
data. And then it will read through every data object for the table
that is still a part of this transaction. We will know which data
objects are still a part of this transaction by reading through all
&lt;code&gt;AddDataobject&lt;/code&gt; actions. A future version of this project would also
eliminate data object files from the list by observing
&lt;code&gt;DeleteDataobject&lt;/code&gt; actions. But we don't do that in this post.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;scanIterator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;allActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;allActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddDataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AddDataobject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedData&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;scanIterator&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;unflushedRowsLen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedDataPointer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;scanIterator&lt;/code&gt; needs to track where we are in in-memory rows, in
data objects, and within a particular data object.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scanIterator&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;struct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// First we iterate through unflushed rows.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;DATAOBJECT_SIZE&lt;/span&gt;&lt;span class="p"&gt;][]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedRowsLen&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;unflushedRowPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Then we move through each dataobject.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dataobjectsPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// And within each dataobject we iterate through rows.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="w"&gt;           &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dataobjectRowPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And the &lt;code&gt;scanIterator&lt;/code&gt; will be driven by a &lt;code&gt;next()&lt;/code&gt; method that goes
through in-memory data first and then through what's on disk.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;readDataobject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_table_%s_%s&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;do&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nx"&gt;do&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// returns (nil, nil) when done&lt;/span&gt;
&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;scanIterator&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Iterate through in-memory rows first.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedRowPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedRowsLen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedRows&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedRowPointer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unflushedRowPointer&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// If we've gotten through all dataobjects on disk we're done.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectsPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjects&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectsPointer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readDataobject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectRowPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Len&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectsPointer&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectRowPointer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectRowPointer&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;si&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataobjectRowPointer&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's it for scanning a table! The final piece of the puzzle is
committing a transaction.&lt;/p&gt;
&lt;h3 id="committing-a-transaction"&gt;Committing a transaction&lt;/h3&gt;&lt;p&gt;When we commit a transaction we must flush any remaining data. A
read-only transaction (one which has no &lt;code&gt;Actions&lt;/code&gt;) is immediately
done. There is no concurrency check.&lt;/p&gt;
&lt;p&gt;Otherwise we will serialize transaction state and attempt to
atomically &lt;code&gt;putIfAbsent&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The only way this will fail is if there is another concurrent writer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;errNoTx&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Flush any outstanding data&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tables&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flushRows&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;table&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;wrote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Actions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;wrote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Read-only transaction, no need to do a concurrency check.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;wrote&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;_log_%020d&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// We won't store previous actions, they will be recovered on&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// new transactions. So unset them. Honestly not totally&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// clear why.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;previousActions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Marshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;putIfAbsent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;d&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tx&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;unimplemented&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is the crux of Delta Lake. It's simple. And honestly it's a bit
shocking. Real Delta Lake does support automatic retries in some
cases. But primarily you are limited to a single writer per table,
even if the writers are writing non-conflicting rows. Iceberg is
basically the same here, it's just how metadata is tracked that
differs.&lt;/p&gt;
&lt;p class="note"&gt;
  As mentioned in another note above, our implementation is actually
  stricter than Delta Lake since it manages all table transaction logs
  together. This means you can get snapshot isolation across all
  tables (which Delta Lake doesn't support) but it will mean
  significantly more contention and failed write transactions.
&lt;/p&gt;&lt;p&gt;The Delta Lake and Iceberg folks apparently wanted to avoid
FoundationDB (i.e. the Snowflake architecture, which is mentioned in
the Delta Lake paper) so much that they'd give up row-level
concurrency to be mostly serverless.&lt;/p&gt;
&lt;p&gt;Is it worth it? Dunno. Delta Lake and Iceberg are getting massive
adoption. Many very smart people have worked, and continue to work, on
both. Moreover it is apparently what the market wants. Every
database-like product is implementing, or is planning to implement,
Delta Lake or Iceberg.&lt;/p&gt;
&lt;h3 id="trying-it-out"&gt;Trying it out&lt;/h3&gt;&lt;p&gt;Let's add a test in &lt;code&gt;main_test.go&lt;/code&gt; to see what happens with concurrent
writers. Follow the comments and debug logs for details:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;package&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;main&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;os&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;testing&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestConcurrentTableWriters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MkdirTemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;test-database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newFileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c2Writer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Have c2Writer start up a transaction.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not start first c2 tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2] new tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// But then have c1Writer start a transaction and commit it first.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not start first c1 tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1] new tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not create x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1] Created table&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Joey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write first row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1] Wrote row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Yue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write second row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1] Wrote row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not commit tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1] Committed tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Now go back to c2 and write data.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not create x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2] Created table&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Holly&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write first row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2] Wrote row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;concurrent commit must fail&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2] tx not committed&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Try it out:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;mod&lt;span class="w"&gt; &lt;/span&gt;init&lt;span class="w"&gt; &lt;/span&gt;otf
&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;mod&lt;span class="w"&gt; &lt;/span&gt;tidy
&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-run&lt;span class="w"&gt; &lt;/span&gt;TestConcurrentTableWriters&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;--debug
&lt;span class="go"&gt;[DEBUG] [c2] new tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1] new tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1] Created table&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1] Wrote row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1] Wrote row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1] Committed tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2] Created table&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2] Wrote row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2] tx not committed&lt;/span&gt;
&lt;span class="go"&gt;PASS&lt;/span&gt;
&lt;span class="go"&gt;ok      otf 0.311s&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's pretty cool.&lt;/p&gt;
&lt;p&gt;And what about a reader and concurrent writer? Observe that the reader
always reads a snapshot. Follow the comments again for detail:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;TestConcurrentReaderWithWriterReadsSnapshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;testing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MkdirTemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;test-database&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;panic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newFileObjectStorage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;c2Reader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fos&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// First create some data and commit the transaction.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not start first c1 tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Started tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;b&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not create x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Created table&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Joey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write first row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Wrote row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Yue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write second row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Wrote row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not commit tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Committed tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Now start a new transaction for more edits.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not start second c1 tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Starting new write tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Before we commit this second write-transaction, start a&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// read transaction.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not start c2 tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2Reader] Started tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Write and commit rows in c1.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Ada&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not write third row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Wrote third row&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Scan x in read-only transaction&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not scan x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2Reader] Started scanning&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not iterate x scan&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2Reader] Done scanning&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2Reader] Got row in reader tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Joey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Yue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected two rows&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Scan x in c1 write transaction&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;x&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not scan x in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Started scanning&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not iterate x scan in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Done scanning&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Got row in tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Ada&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;// Since this hasn't been serialized to JSON, it's still an int not a float.&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Joey&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;Yue&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;row mismatch in c1&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;expected three rows&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Writer committing should succeed.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c1Writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not commit second tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c1Writer] Committed tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Reader committing should succeed.&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;c2Reader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;commitTx&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;assertEq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;quot;could not commit read-only tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;[c2Reader] Committed tx&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Run it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;go&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;test&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-run&lt;span class="w"&gt; &lt;/span&gt;TestConcurrentReaderWithWriterReadsSnapshot&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="w"&gt; &lt;/span&gt;--debug
&lt;span class="go"&gt;[DEBUG] [c1Writer] Started tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Created table&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Wrote row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Wrote row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Committed tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Starting new write tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Started tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Wrote third row&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Started scanning&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Got row in reader tx [Joey 1]&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Got row in reader tx [Yue 2]&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Done scanning&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Started scanning&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Got row in tx [Ada 3]&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Got row in tx [Joey 1]&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Got row in tx [Yue 2]&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Done scanning&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c1Writer] Committed tx&lt;/span&gt;
&lt;span class="go"&gt;[DEBUG] [c2Reader] Committed tx&lt;/span&gt;
&lt;span class="go"&gt;PASS&lt;/span&gt;
&lt;span class="go"&gt;ok      otf 0.252s&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Sweet.&lt;/p&gt;
&lt;h3 id="what's-next?"&gt;What's next?&lt;/h3&gt;&lt;p&gt;As mentioned, we didn't touch a lot of things. Handling updates and
deletes, transaction log checkpoints, data object compaction, etc.&lt;/p&gt;
&lt;p&gt;Take a close look at the &lt;a href="https://www.vldb.org/pvldb/vol13/p3411-armbrust.pdf"&gt;Delta Lake
paper&lt;/a&gt; and the
&lt;a href="https://github.com/delta-io/delta/blob/master/PROTOCOL.md"&gt;Delta Lake
Spec&lt;/a&gt; and
see what you can do!&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Build a serverless ACID database with this one neat trick.&lt;br /&gt;&lt;br /&gt;(New blog post)&lt;a href="https://t.co/rHgfKSPY6q"&gt;https://t.co/rHgfKSPY6q&lt;/a&gt; &lt;a href="https://t.co/1hmjsxIk6w"&gt;pic.twitter.com/1hmjsxIk6w&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1840474893491560777?ref_src=twsrc%5Etfw"&gt;September 29, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sun, 29 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-09-29-build-a-serverless-acid-database-with-this-one-neat-trick.html</guid></item><item><title>Techtalk: Why are A/B tests the gold standard of causal inference?</title><link>https://bytepawn.com/techtalk-ab-testing.html</link><description>&lt;p&gt;Recently, I delivered a techtalk on A/B testing to an audience of non-technical Product Managers and experienced Data Scientists. &lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/abtest-google-slides.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 29 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/techtalk-ab-testing.html</guid></item><item><title>TIL how to test CORS on the command line with curl</title><link>https://www.zufallsheld.de/2024/09/28/til-how-to-test-CORS-on-the-command-line-with-curl/</link><description>&lt;p&gt;In my &lt;a href="https://www.zufallsheld.de/2024/09/24/til-how-to-configure-additional-heades-in-gitlabs-nginx/"&gt;last &lt;span class="caps"&gt;TIL&lt;/span&gt;&lt;/a&gt; I talked about how to set additional security headers for Gitlab.
But I also had to do this for other applications I was supporting, where it was more straight-forward to do it (meaning: with&amp;nbsp;code).&lt;/p&gt;
&lt;p&gt;I needed to set&amp;nbsp;the &lt;code&gt;access-control-allow-origin&lt;/code&gt; header in the other applications …&lt;/p&gt;</description><author>zufallsheld</author><pubDate>Sat, 28 Sep 2024 22:30:00 GMT</pubDate><guid isPermaLink="true">https://www.zufallsheld.de/2024/09/28/til-how-to-test-CORS-on-the-command-line-with-curl/</guid></item><item><title>Coding Exercise: Board games</title><link>/post/boardgames-exercise/</link><description>&lt;p&gt;It turns out that modeling the rules of Chess and Checkers in code is a pretty
good way to teach and learn some programming concepts.&lt;/p&gt;
&lt;p&gt;Inspired by Chapter &lt;em&gt;2.4 &amp;quot;Abstracting a Domain&amp;quot;&lt;/em&gt; of &lt;em&gt;&lt;a href="https://mitpress.mit.edu/9780262045490/software-design-for-flexibility/"&gt;&amp;quot;Software Design for Flexibility&amp;quot;&lt;/a&gt;&lt;/em&gt; by Chris Hanson and Gerald Jay Sussman, I've spent more hours than I want to admit in writing &lt;a href="https://neuroning.com/boardgames-exercise/"&gt;a code walkthrough of one
possible solution&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I might soon start looking for another similarly &lt;em&gt;didactic&lt;/em&gt; modeling problem to work on.&lt;/p&gt;</description><author>Neuroning</author><pubDate>Sat, 28 Sep 2024 22:10:00 GMT</pubDate><guid isPermaLink="true">/post/boardgames-exercise/</guid></item><item><title>The Privacy Dilemma</title><link>https://camhashemi.com/essays/privacy-dilemma/</link><description>&lt;p&gt;Software products face a fundamental tension between utility and privacy.&lt;/p&gt;&lt;p&gt;Given two of software&amp;rsquo;s essential functions are data storage and processing, software companies tend to become more valuable as they collect more data. In an ideal world, this value translates directly to customer value. But in reality, each piece of data we share has a chance of being used against our best interests, whether by a third-party or the company itself.&lt;/p&gt;&lt;p&gt;Our best answer to the privacy dilemma seems to be something like: &amp;ldquo;a good enough product outweighs the risk.&amp;rdquo; But it&amp;rsquo;s not a solution.&lt;/p&gt;&lt;p&gt;While GDPR and other privacy standards try their best, they&amp;rsquo;re too difficult to enforce: their governing bodies simply don&amp;rsquo;t have the resources.&lt;/p&gt;&lt;p&gt;Even if a company wanted to do the right thing by taking a state-of-the-art privacy approach, this incurs a significant development and operational cost, one which could easily prove fatal for all but the most stable enterprises.&lt;/p&gt;&lt;p&gt;Privacy is like a lot of social goods: valuable, easy to demand, but difficult to achieve organically. As with many social contracts, our best solution is the crude-but-effective policy of trusting proactively and punishing reactively.&lt;/p&gt;&lt;p&gt;When dreaming of software products, I&amp;rsquo;ve often gotten stuck on the privacy dilemma.&lt;/p&gt;&lt;p&gt;The big question is whether privacy is solvable: is it more like a law of physics, or is it just a puzzle we haven&amp;rsquo;t solved yet?&lt;/p&gt;</description><author>Cam Hashemi</author><pubDate>Sat, 28 Sep 2024 17:16:52 GMT</pubDate><guid isPermaLink="true">https://camhashemi.com/essays/privacy-dilemma/</guid></item><item><title>Clever, Brave, Persistent</title><link>https://camhashemi.com/essays/clever-brave-persistent/</link><description>&lt;p&gt;I wonder: is there anything more predictive of success than being clever, brave, and persistent?&lt;/p&gt;&lt;p&gt;Clever means we see opportunities.Brave means we go for them.Persistent means we don&amp;rsquo;t give up.&lt;/p&gt;&lt;h2 id="two-aint-enough"&gt;Two Ain&amp;rsquo;t Enough&lt;/h2&gt;&lt;p&gt;&amp;ldquo;Brave and clever&amp;rdquo; tries good ideas but quits too early. They see good opportunities and are thrilled to get started, but as challenges naturally arise, they&amp;rsquo;re unable to rekindle their bravery and cleverness to overcome them. They either silently quit or find something else to get excited by.&lt;/p&gt;&lt;p&gt;&amp;ldquo;Clever and persistent&amp;rdquo; does good work but never leads. Even when they see clear opportunities, they&amp;rsquo;re afraid to speak their mind. This fear is especially present when their ideas go against the grain, though that&amp;rsquo;s when clever ideas are most valuable.&lt;/p&gt;&lt;p&gt;&amp;ldquo;Brave and persistent&amp;rdquo; tries a lot of bad ideas and doesn&amp;rsquo;t quit them soon enough. We admire their confidence, despite rolling our eyes after hearing about their next big thing.&lt;/p&gt;&lt;p&gt;&amp;ldquo;Brave, clever, and persistent&amp;rdquo; will see opportunities, go for them, and stick with them until they succeed. We want them on our teams, if not leading them.&lt;/p&gt;&lt;h2 id="more-decisions-than-skills"&gt;More Decisions Than Skills&lt;/h2&gt;&lt;p&gt;There&amp;rsquo;s something special about cleverness, bravery, and persistence. They&amp;rsquo;re not skills, they&amp;rsquo;re more like decisions.&lt;/p&gt;&lt;p&gt;Communication is a skill. In some domains, it&amp;rsquo;s a very important skill. But as a skill, there are techniques we need to learn and practice to become skilled.&lt;/p&gt;&lt;p&gt;Bravery isn&amp;rsquo;t a skill, it&amp;rsquo;s how we measure a decision. A lifelong coward can become a hero with a single brave decision.&lt;/p&gt;&lt;p&gt;Persistence isn&amp;rsquo;t a skill either: it&amp;rsquo;s a series of decisions, none of which include quitting. One hard-earned success can make up for a lifetime of early defeats.&lt;/p&gt;&lt;p&gt;Not even cleverness is a skill. There are no standard techniques for being clever: sometimes cleverness comes from thinking outside of the box and other times it comes from sticking to the fundamentals. Cleverness is about seeing opportunities, and opportunities are context-specific.&lt;/p&gt;&lt;p&gt;Cleverness isn&amp;rsquo;t a decisive quality, but it&amp;rsquo;s driven by a decision. You can&amp;rsquo;t be clever unconsciously or without caring. You need to decide to look where others aren&amp;rsquo;t looking.&lt;/p&gt;&lt;p&gt;If someone is clever, brave, or persistent, it&amp;rsquo;s only because they have a history of clever, brave, or persistent decisions. Any significant challenge can test a lifetime of cleverness, bravery, or persistence. In an important sense, we&amp;rsquo;re only as clever, brave, or persistent as our next decision to go for it instead of giving up.&lt;/p&gt;&lt;p&gt;Compared to skills, cleverness, bravery, and persistence are enacted on an even playing field. We&amp;rsquo;re constantly faced with decisions to be clever, brave, or persistent, and we only need one big success to be identified with those qualities.&lt;/p&gt;&lt;h2 id="essential-will"&gt;Essential Will&lt;/h2&gt;&lt;p&gt;You can explain cleverness in terms of bravery and persistence. To be clever, you often need to be brave enough to decide to look for an unseen opportunity, persistent enough to find it, and then brave enough to seize it.&lt;/p&gt;&lt;p&gt;You can also explain persistence in terms of cleverness and bravery. Sometimes, persistence is about being clever enough to see a better option than quitting. But often times, persistence is just about being brave enough to find &lt;em&gt;any&lt;/em&gt; option besides quitting, no matter how bleak it may seem.&lt;/p&gt;&lt;p&gt;Essentially, each of these qualities is a different form of will. Cleverness is the will to see, bravery is the will to act, and persistence is the will to continue.&lt;/p&gt;&lt;p&gt;Will is our power to decide, and bravery, cleverness, and persistence are qualities of the decisions we make.&lt;/p&gt;&lt;p&gt;Should we then rather say that will is the most important quality for success? If we had to pick one, then yes.&lt;/p&gt;&lt;p&gt;But bravery, cleverness, and persistence sit at the right level of specificity. We can distinguish these specific qualities easier in ourselves and others, and we can practice them more intentionally.&lt;/p&gt;&lt;h2 id="the-trifecta"&gt;The Trifecta&lt;/h2&gt;&lt;p&gt;Even if we aren&amp;rsquo;t strong in all three qualities, the trifecta tells us who to partner with and why. While individual success is difficult without all three qualities, we can easily achieve shared success by partnering with people who excel at what were missing. Rather than despising people who are too risky, too contrarian, or too stubborn for our liking, we can focus on how these qualities as essential for success. By complementing each other, we can achieve greater success together than we can as individuals.&lt;/p&gt;&lt;p&gt;Another elegant aspect of the trifecta is that it&amp;rsquo;s predictive of success while being open to many degrees of freedom. Extravert or introvert? Agreeable or disagreeable? Creative or operational? Marketeer or engineer?&lt;/p&gt;&lt;p&gt;Come as you are - our success is generally the result of our decisions to be clever, brave, and persistent.&lt;/p&gt;</description><author>Cam Hashemi</author><pubDate>Sat, 28 Sep 2024 16:28:46 GMT</pubDate><guid isPermaLink="true">https://camhashemi.com/essays/clever-brave-persistent/</guid></item><item><title>Redis Streams: Ultimate Guide to Real-Time Data Processing</title><link>https://engineeringatscale.substack.com/p/redis-streams-guide-real-time-data-processing</link><description>Dive deep into Redis Streams, pros, cons and real-world use cases</description><author>Engineering At Scale</author><pubDate>Sat, 28 Sep 2024 14:39:10 GMT</pubDate><guid isPermaLink="true">https://engineeringatscale.substack.com/p/redis-streams-guide-real-time-data-processing</guid></item><item><title>Booksbooksbooks 09/24</title><link>https://goodinternet.substack.com/p/booksbooksbooks-0924</link><description>Tiny reviews for books from Percival Everett, Iain Banks, Herman Melville, Frank Herbert, Adrian Tchaikovsky and many more.</description><author>GOOD INTERNET</author><pubDate>Sat, 28 Sep 2024 12:47:11 GMT</pubDate><guid isPermaLink="true">https://goodinternet.substack.com/p/booksbooksbooks-0924</guid></item><item><title>16 years later</title><link>https://whackylabs.com/rant/2024/09/28/16-years-later/</link><description>&lt;p&gt;So today I completed my 16 years of working professionally. This is a quick summary of the past year.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Improved my javascript skills by deep diving into the javascript language design.&lt;/li&gt;
  &lt;li&gt;Understood more about css. Things like animation, grid layout and just more basic things that I guess almost all web devs are supposed to know.&lt;/li&gt;
  &lt;li&gt;Made a static portfolio webpage first using old school html-css-js and then again with react.js.&lt;/li&gt;
  &lt;li&gt;Made a few prototypes using react-native and expo.&lt;/li&gt;
  &lt;li&gt;Tried out new cross-platform mobile frameworks like .NET MAUI and Ionic.&lt;/li&gt;
  &lt;li&gt;Made a small api only web service with MEN stack: mongodb, express.js, node.js.&lt;/li&gt;
  &lt;li&gt;Rewrote my web service using AWS lambda, http-gateway, cognito, s3 and dynamodb.&lt;/li&gt;
  &lt;li&gt;Expanded on my knowledge on SwiftUI.&lt;/li&gt;
  &lt;li&gt;Started playing around with Swift structured concurrency. I mean like I had to with Xcode 16/swift 6 migration at our face.&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Sat, 28 Sep 2024 11:36:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/rant/2024/09/28/16-years-later/</guid></item><item><title>Chip Fabrication in India: Tata Electronics’ $14 Billion Investment</title><link>https://nitinnain.com/chip-fabrication-in-india-tata-electronics-14-billion-investment/</link><description>After years of unsuccessful attempts by various players to establish semiconductor fabrication in India, it seems the tide is finally turning: Tata Electronics Private Limited (est. 2020) has started investments in four manufacturing facilities spread across India. There’s also the China + 1 strategy at play here, where countries are trying to reduce dependence on [&amp;#8230;]</description><author>Nitin Nain</author><pubDate>Sat, 28 Sep 2024 08:20:04 GMT</pubDate><guid isPermaLink="true">https://nitinnain.com/chip-fabrication-in-india-tata-electronics-14-billion-investment/</guid></item><item><title>Understanding Foreign Keys in PostgreSQL</title><link>https://dylanpaulus.com/posts/2024/understanding-postgres-foreign-keys/</link><description>&lt;p&gt;Foreign keys in PostgreSQL are a fundamental way of defining relationships between tables. Splitting data across multiple tables is foundational to relational databases and data normalization. However, when spreading related data across various tables, we must ensure that values in one table are valid and exist in another. Enter foreign keys.&lt;/p&gt;</description><author>Dylan Paulus' Blog</author><pubDate>Sat, 28 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://dylanpaulus.com/posts/2024/understanding-postgres-foreign-keys/</guid></item><item><title>Deploying Metabase as a Quadlet: A Rootless Podman Journey</title><link>https://tsak.dev/posts/metabase-quadlet/</link><description>&lt;p&gt;Recently I have been getting into Podman as a great (rootless) Docker alternative and its neat integration into
Redhat based Linux distributions, rekindling my decade old love affair with &lt;a href="https://en.wikipedia.org/wiki/Red_Hat_Linux"&gt;Red Hat Linux&lt;/a&gt;.
Being asked to deploy &lt;a href="https://www.metabase.com/"&gt;Metabase&lt;/a&gt; (Open-source Edition) as an internal service at my place of
work, I decided to give the somewhat new Quadlets a try in deploying the service, instead of the older, deprecated way
of asking Podman to generate &lt;code&gt;systemd&lt;/code&gt; unit files.&lt;/p&gt;</description><author>Look mum, I have a blog on tsak.dev</author><pubDate>Sat, 28 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://tsak.dev/posts/metabase-quadlet/</guid></item><item><title>Using nginx to secure hidden content with a bit of cookie magic</title><link>https://tsak.dev/posts/nginx-secure-hidden-content-with-cookie-magic/</link><description>&lt;p&gt;Sometimes I host things that I don&amp;rsquo;t want to be readily available on the open internet (or at least not easy
to discover). It might be something that could do with an extra layer of obscurity. It might be a piece of third-party
software to which I have no good insight into its security posture.&lt;/p&gt;
&lt;p&gt;Below is a neat trick to achieve this with a bit of &lt;a href="https://nginx.org/en/"&gt;nginx&lt;/a&gt; configuration magic alone, by
&amp;ldquo;misusing&amp;rdquo; the &lt;code&gt;ngx_http_geo_module&lt;/code&gt;. Requests other than the secret location to
&lt;a href="https://en.wikipedia.org/wiki/Open_sesame"&gt;open sesame&lt;/a&gt; will be rejected unless either the secret cookie is
part of the request headers, or a request is coming from a trusted network.&lt;/p&gt;</description><author>Look mum, I have a blog on tsak.dev</author><pubDate>Sat, 28 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://tsak.dev/posts/nginx-secure-hidden-content-with-cookie-magic/</guid></item><item><title>200 articles</title><link>https://bytepawn.com/200-articles.html</link><description>&lt;p&gt;A review and introspect on the second 100 articles written on Bytepawn. &lt;br /&gt;&lt;br /&gt;&lt;img alt="100" src="/images/200.jpg" style="width: 300px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sat, 28 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/200-articles.html</guid></item><item><title>Blog: The Third</title><link>https://friendshipcastle.zip/blog/blog3?utm_campaign=feed&amp;utm_source=blog.rss</link><description>Next.js is my new friend</description><author>Twilight Sparkle's Friendship Castle</author><pubDate>Fri, 27 Sep 2024 16:00:00 GMT</pubDate><guid isPermaLink="true">https://friendshipcastle.zip/blog/blog3?utm_campaign=feed&amp;utm_source=blog.rss</guid></item><item><title>Roast #41: V7 reflection + survey results</title><link>https://thoughtfulcoffeenyc.substack.com/p/roast-41-v7-reflection-survey-results</link><description>Will trade coffee for fantasy football advice</description><author>thoughtfulcoffee</author><pubDate>Fri, 27 Sep 2024 15:06:02 GMT</pubDate><guid isPermaLink="true">https://thoughtfulcoffeenyc.substack.com/p/roast-41-v7-reflection-survey-results</guid></item><item><title>What is a photo?</title><link>https://ilearnt.com/blog/whatisaphoto/</link><description>&lt;p&gt;Back in the mists of time if you wanted to take a photograph you would have to use a camera that you had to carry with you.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Fri, 27 Sep 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/whatisaphoto/</guid></item><item><title>Rewriting Rust: A Response</title><link>https://gavinhoward.com/2024/09/rewriting-rust-a-response/</link><description>Joseph Gentle wrote a post on how he wants Rust to change. I am already working on that.</description><author>Gavin D. Howard</author><pubDate>Fri, 27 Sep 2024 10:45:49 GMT</pubDate><guid isPermaLink="true">https://gavinhoward.com/2024/09/rewriting-rust-a-response/</guid></item><item><title>Diskprices Q&amp;amp;amp;A</title><link>https://synack.me/blog/diskprices-qa.html</link><description>I received an email today with some questions about diskprices.com. I get some of these pretty frequently, so I thought it would be good to share the responses more widely. Shared with permission from Mr. Doyle.</description><author>Jeremy Grosser</author><pubDate>Fri, 27 Sep 2024 10:00:00 GMT</pubDate><guid isPermaLink="true">https://synack.me/blog/diskprices-qa.html</guid></item><item><title>Introduction to prompt engineering</title><link>https://blog.adnansiddiqi.me/introduction-to-prompt-engineering/</link><description>&lt;p&gt;This post is part of the GenAI Series The only way we can interact with an LLM model is by passing an instruction to retrieve a response. That instruction is called a prompt. In this post, we are going to discuss what a prompt is and what prompt engineering entails What is a Prompt A prompt is simply the instruction or question you give to an AI or language model to get a response. Think of it as a way to guide the AI toward the answer or output you want. Imagine you&amp;#8217;re talking to a super-smart friend who&amp;#8217;s always eager to help but needs a little guidance on what you want. The prompt is how you start that conversation and steer it in the right direction. It could be as simple as asking, &amp;#8220;What&amp;#8217;s the weather like today?&amp;#8221; or as complex as, &amp;#8220;Write me a short story about a time-traveling chef.&amp;#8221; Just like how you might phrase things differently depending on who you&amp;#8217;re talking to, crafting a good prompt is about finding the right words to communicate clearly with AI, so it understands exactly what you&amp;#8217;re after and can give you the best possible response.. What is Prompt Engineering Prompt engineering is the art of crafting the perfect question or instruction to get the best possible answer from an AI. Think of it as learning how to be a really good coach or teacher for AI. You&amp;#8217;re not just asking questions or giving instructions — you&amp;#8217;re crafting them carefully to bring out the AI&amp;#8217;s best performance. It&amp;#8217;s like knowing exactly how to explain a task to a friend so they not only get it right but might even surprise you with how well they do it. Components of a prompt The basic components of a prompt are: Instruction: This is the main task you&amp;#8217;re giving the AI. It&amp;#8217;s like telling a friend what you need help with. Example: Create a recipe for a vegetarian pasta dish. Context: Any background info that helps the AI understand the situation better. Think of it as filling your friend in on what&amp;#8217;s going on. Example: I&amp;#8217;m cooking for a dinner party where one guest is allergic to nuts. Input: The specific information or data you&amp;#8217;re giving the AI to work with. It&amp;#8217;s the raw material for the task. Example: I have tomatoes, spinach, garlic, and various pasta shapes in my pantry. Output format: How you want the AI to present its answer. Like asking your friend to give their advice in bullet points or as a story. Example: Present the recipe in a numbered list with ingredients followed by steps. Examples (sometimes): Showing the AI what you mean by giving it a sample. It&amp;#8217;s like saying, &amp;#8220;Here&amp;#8217;s what I&amp;#8217;m looking for.&amp;#8221; Examples: Ingredients: 1 cup flour 2 eggs Steps: Instructions Mix flour and eggs Knead the dough Constraints The recipe should not take more than 30 minutes to prepare and cook Putting it all together, a complete prompt might look like this: Create a recipe for a vegetarian pasta dish. I&amp;#8217;m cooking for a dinner party where one guest is allergic to nuts. I have tomatoes, spinach, garlic, and various pasta shapes in my pantry. Present the recipe in a numbered list with ingredients followed by steps. The recipe should take no more than 30 minutes to prepare and cook. Here&amp;#8217;s a sample format: Ingredients: &amp;#8211; 1 cup flour &amp;#8211; 2 eggs Steps: Instructions: &amp;#8211; Mix flour and eggs &amp;#8211; Knead the dough Types of Prompts Zero-shot prompts: A direct question or instruction without providing any examples. It is like asking your smart friend to do something they&amp;#8217;ve never done before, but you trust they can figure it out. Example: What is the capital of France? One-Shot Prompts: These prompts provide a single example to the AI model before asking a related question. It&amp;#8217;s like you&amp;#8217;re giving your friend a couple of examples to follow. It&amp;#8217;s like saying, &amp;#8220;Here&amp;#8217;s how it&amp;#8217;s done, now you try!&amp;#8221;. Example: A cat is a pet. A dog is a pet. What is a rabbit? Chain-of-thought prompts: These prompts encourage the AI to break down a complex problem into smaller, more manageable steps, guiding it toward a solution. This is when you ask your AI friend to show their work. It&amp;#8217;s like saying, &amp;#8220;Walk me through how you&amp;#8217;re thinking about this.&amp;#8221;. Example: If I have 2 apples and buy 3 more, how many apples do I have in total? First, add the apples together, then give the total. In essence: Zero-shot: &amp;#8220;Here&amp;#8217;s a task. Do it.&amp;#8221; One-shot: &amp;#8220;Here&amp;#8217;s an example. Do a similar task.&amp;#8221; Chain-of-thought: &amp;#8220;Here&amp;#8217;s a task. Break it down into smaller steps and solve it.&amp;#8221; Conclusion In this post, we learned not only what prompt and prompt engineering is but also which type of prompt is suitable for different situations. You can&amp;#8217;t utilize an LLM effectively unless you know how to pass the right instructions. If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/introduction-to-prompt-engineering/"&gt;Introduction to prompt engineering&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Fri, 27 Sep 2024 08:00:16 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/introduction-to-prompt-engineering/</guid></item><item><title>ChatGPT's critique of OpenAI CEO Sam Altman's vision of the AI future</title><link>https://bytepawn.com/sam-altman-the-intelligence-age.html</link><description>&lt;p&gt;A few days ago OpenAI CEO Sam Altman published an article titled The Intelligence Age. I used OpenAI's new &lt;code&gt;o1-mini&lt;/code&gt; model to critique Sam Altman's writing of its own future. &lt;br /&gt;&lt;br /&gt; &lt;img alt="" src="/images/outlook-2024-1.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Fri, 27 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/sam-altman-the-intelligence-age.html</guid></item><item><title>Automatic Speech Recognition for Maltese - Part 2</title><link>https://simonam.dev/asr-for-maltese-02/</link><description>This is part 2 of my effort to create an open-weights Automatic Speech Recognition model for my native language Maltese. Before the how, let…</description><author>Simon's Blog</author><pubDate>Fri, 27 Sep 2024 00:30:00 GMT</pubDate><guid isPermaLink="true">https://simonam.dev/asr-for-maltese-02/</guid></item><item><title>ktls now under the rustls org</title><link>https://fasterthanli.me/articles/ktls-now-under-rustls-org</link><description>&lt;a class="anchor" href="#what-s-a-ktls" id="what-s-a-ktls"&gt;&lt;h2&gt;What’s a ktls&lt;/h2&gt;&lt;/a&gt;
&lt;p&gt;I started work on &lt;a href="https://lib.rs/crates/ktls"&gt;ktls&lt;/a&gt; and &lt;a href="https://lib.rs/crates/ktls-sys"&gt;ktls-sys&lt;/a&gt;,
a pair of crates exposing &lt;a href="https://www.kernel.org/doc/html/latest/networking/tls-offload.html"&gt;Kernel TLS offload&lt;/a&gt;
to Rust, about &lt;a href="https://github.com/rustls/ktls/commit/798466d7c3e659ecdf035afac0bea2b679aea4c4"&gt;two years ago&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;kTLS lets the kernel (and, in turn, any network interface that supports it) take care of encryption, framing,
etc., for the entire duration of a TLS connection… as soon as you &lt;em&gt;have&lt;/em&gt; a TLS connection.&lt;/p&gt;

&lt;p&gt;For the handshake itself (hellos, change cipher, encrypted extensions,
certificate verification, etc.), you still have to use a userland TLS
implementation.&lt;/p&gt;







&lt;a class="anchor" href="#the-ktls-api-today" id="the-ktls-api-today"&gt;&lt;/a&gt;




&lt;a class="anchor" href="#coordinating-collaborating" id="coordinating-collaborating"&gt;&lt;/a&gt;</description><author>fasterthanli.me</author><pubDate>Fri, 27 Sep 2024 00:10:00 GMT</pubDate><guid isPermaLink="true">https://fasterthanli.me/articles/ktls-now-under-rustls-org</guid></item><item><title>Million dollars is not cool, you know what is cool? million rows</title><link>https://newbeelearn.com/blog/million-rows-csv-debug-story/</link><description>TLDR; Story of challenges faced while displaying large csv file with million+ rows in CSV Viewer</description><author>Welcome to my weblog on Newbeelearn</author><pubDate>Thu, 26 Sep 2024 21:31:23 GMT</pubDate><guid isPermaLink="true">https://newbeelearn.com/blog/million-rows-csv-debug-story/</guid></item><item><title>PostScript® 1.0 - A Code Study</title><link>https://ztoz.blog/posts/postscript-code/</link><description>&lt;p&gt;In December 2022, Adobe, through the Computer History Museum (CHM), released the source code for PostScript®, version 1.0. PostScript is one of the foundational technologies of the desktop publishing revolution of the early 1980s, along with laser printers, the graphical user interface of the Apple Macintosh, and Aldus PageMaker. PostScript is a programming language and a page description format for translating visual content into printed documents.&lt;/p&gt;
&lt;p&gt;Adobe immediately enjoyed business success through licensing PostScript to laser printer manufacturers and it became the &lt;em&gt;de facto&lt;/em&gt; digital publishing format. While multiple histories have studied this event through a business lens, what historical questions may be answered through the source code? Further, as software practitioners, what can we learn from the source code to apply to present and future designs?&lt;/p&gt;
&lt;p&gt;We argue:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;PostScript&amp;rsquo;s design and implementation benefited from a long lineage of other software programs (as Adobe has always admitted),&lt;/li&gt;
&lt;li&gt;The software architecture aligned with the interests of creatives, printer services, and printer manufacturers,&lt;/li&gt;
&lt;li&gt;Design choices, including modularity and semantics, added value to the product, and&lt;/li&gt;
&lt;li&gt;Pursuing the &amp;ldquo;print anything&amp;rdquo; objective, rather than page printing throughput, yielded a superior implementation.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;A note on trademarks: PostScript® is a trademark of Adobe Inc. For reading clarity, we will omit the registered trademark symbol for the remainder of the article.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;/h2&gt;
&lt;ol&gt;
	&lt;li&gt;&lt;a href="#background-and-article-outline"&gt;Background and Article Outline&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#timeline"&gt;Timeline&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#developers-and-their-influences"&gt;Developers and their Influences&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#development-and-test-environment"&gt;Development and Test Environment&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#design-and-source-code"&gt;Design and Source Code&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#interesting-algorithms-and-designs"&gt;Interesting Algorithms and Designs&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#alternative-paths--competitors"&gt;Alternative Paths / Competitors&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#print-anything-philosophy"&gt;"Print Anything" Philosophy&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#deployment-and-history-after-version-10"&gt;Deployment and History after Version 1.0&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="#references"&gt;References&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="background-and-article-outline"&gt;Background and Article Outline&lt;/h2&gt;
&lt;p&gt;The origin story of Adobe has been told many times. In short, Charles Geschke and John Warnock left Xerox PARC to co-found Adobe after being frustrated at Xerox&amp;rsquo;s seeming disinterest in marketing their publishing technologies. Steve Jobs needed software for Apple&amp;rsquo;s upcoming laser printer and, after seeing what Adobe was developing, convinced the two founders to license their software. Other laser printers similarly licensed PostScript and with the release of the early Macintosh and Aldus Pagemaker, the desktop publishing revolution was born. Two extended write-ups of this story are Pamela Pfiffner&amp;rsquo;s &lt;a href="https://archive.org/details/insidepublishing00pfif/"&gt;&lt;em&gt;Inside the Publishing Revolution: The Adobe Story&lt;/em&gt;&lt;/a&gt;, which is a 2003 book-length business history, and Tekla Perry&amp;rsquo;s 1988 Spectrum article, &lt;a href="https://spectrum.ieee.org/adobe-postscript"&gt;&lt;em&gt;Inventing Postscript, the Tech That Took the Pain out of Printing&lt;/em&gt;&lt;/a&gt;, which focuses more on the engineering efforts and prior art.&lt;/p&gt;
&lt;p&gt;At its core, the PostScript interpreter reads in a PostScript program that describes the desired printed pages and then the interpreter renders each page as a raster image to a device, typically a printer. Graphics are composed of paths, which may be straight-lines or curves and may be furthered defined by strokes, fills, or masks. The graphics model is described in the &amp;ldquo;Red&amp;rdquo; book, but PostScript closely follows a model by (Warnock and Wyatt 1982) shortly before Adobe was founded. Text is rendered as paths, with some clever techniques required to produce high-quality typography. Long treated as an Adobe trade secret, John Warnock described the font rendering algorithms in an article for the American Philosophical Society (Warnock 2012). The approach is based on modifying the character outlines, subject to constraints (&amp;ldquo;blue&amp;rdquo; and &amp;ldquo;yellow&amp;rdquo; hints) and an erosion process, to better align with the raster grid and aesthetic needs.&lt;/p&gt;
&lt;p&gt;Within this article, we will first present two timelines. The first contextual timeline will place the development of the code in the larger history of the desktop publishing revolution. The second timeline will focus on the development of the source code. Secondly, the article will cover biographical details of the developers and what education and work experiences they brought towards the development. Then, we will cover the development process and resources available to the development team.&lt;/p&gt;
&lt;p&gt;Turning to the source code, we will examine how it is designed and implemented. PostScript pushed forward the state of the art and we will cover some illustrative examples (although Adobe has kept some details as trade secrets and many aspects were developed after the 1.0 release). Finally, we will compare the design to competitor&amp;rsquo;s designs and the impact of Adobe&amp;rsquo;s &amp;ldquo;print anything&amp;rdquo; philosophy.&lt;/p&gt;
&lt;h3 id="postscript-language"&gt;PostScript Language&lt;/h3&gt;
&lt;p&gt;While our study will focus on the PostScript &lt;em&gt;server&lt;/em&gt; or &lt;em&gt;implementation&lt;/em&gt;, it is important to note the &lt;em&gt;language&lt;/em&gt; that the code supports. In (Reid 1988, pg 2), the author states:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The three most important aspects of the PostScript programming language are that it is &lt;em&gt;interpreted&lt;/em&gt;, that it is &lt;em&gt;stack-based&lt;/em&gt;, and that it uses a unique data structure called a &lt;em&gt;dictionary&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The interpreted nature of the language made it simpler to produce and understand (since you did not need any decompiler to read the code) as well as encouraging experimentation via interactive development. The stack-based model reduced the execution requirements, particularly for memory. Although the Apple LaserWriter infamously had more processing power and memory than the connected Macintosh, resources were at a premium. Further, the content of the stack could be easily modified and composed, allowing programmers great flexibility. The dictionaries allowed existing operators to be replaced or augmented (and then reset to normal for the next job), further providing flexibility to the users.&lt;/p&gt;
&lt;p&gt;To give a sense of the language, the code below (&lt;a href="https://rosettacode.org/wiki/Sierpinski_triangle/Graphical#PostScript"&gt;source: Rosetta Code&lt;/a&gt;) produces a Sierpinski Triangle. Depending on the output device, this will render either to a printed page or a display (Figure A).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-postscript"&gt;&lt;span style="color: #75715e;"&gt;%!PS
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;&lt;/span&gt;
/sierp { &lt;span style="color: #75715e;"&gt;% level ax ay bx by cx cy
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;&lt;/span&gt;    &lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;cpy&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;triangle&lt;/span&gt;
    &lt;span style="color: #a6e22e;"&gt;sierpr&lt;/span&gt;
} &lt;span style="color: #a6e22e;"&gt;bind&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;def&lt;/span&gt;

/sierpr {
    &lt;span style="color: #ae81ff;"&gt;12&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;cpy&lt;/span&gt;
    &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;-4&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt; {
        &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;exch&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;-1&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt;
        &lt;span style="color: #a6e22e;"&gt;add&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0.5&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;mul&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt;
        &lt;span style="color: #a6e22e;"&gt;add&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0.5&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;mul&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;-1&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt;
        &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt;
    } &lt;span style="color: #a6e22e;"&gt;for&lt;/span&gt;       &lt;span style="color: #75715e;"&gt;% l a b c bc ac ab
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;&lt;/span&gt;    &lt;span style="color: #ae81ff;"&gt;13&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;-1&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;dup&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;gt&lt;/span&gt; {
        &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;sub&lt;/span&gt;
        &lt;span style="color: #a6e22e;"&gt;dup&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;cpy&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;18&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;-2&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;sierpr&lt;/span&gt;
        &lt;span style="color: #a6e22e;"&gt;dup&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;index&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;index&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;cpy&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;16&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;-2&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;sierpr&lt;/span&gt;
        &lt;span style="color: #ae81ff;"&gt;9&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;index&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;index&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;cpy&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;13&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;sierpr&lt;/span&gt;
    } { &lt;span style="color: #ae81ff;"&gt;13&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;-6&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;roll&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;7&lt;/span&gt; { &lt;span style="color: #a6e22e;"&gt;pop&lt;/span&gt; } &lt;span style="color: #a6e22e;"&gt;repeat&lt;/span&gt; } &lt;span style="color: #a6e22e;"&gt;ifelse&lt;/span&gt;
    &lt;span style="color: #a6e22e;"&gt;triangle&lt;/span&gt;
} &lt;span style="color: #a6e22e;"&gt;bind&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;def&lt;/span&gt;

/cpy { { &lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;index&lt;/span&gt; } &lt;span style="color: #a6e22e;"&gt;repeat&lt;/span&gt; } &lt;span style="color: #a6e22e;"&gt;bind&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;def&lt;/span&gt;

/triangle {
    &lt;span style="color: #a6e22e;"&gt;newpath&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;moveto&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;lineto&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;lineto&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;closepath&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;stroke&lt;/span&gt;
} &lt;span style="color: #a6e22e;"&gt;bind&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;def&lt;/span&gt;

&lt;span style="color: #ae81ff;"&gt;6&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;50&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;100&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;550&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;100&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;300&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;533&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;sierp&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;showpage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;


&lt;figure&gt;
  
  &lt;img alt="Triangle composed of many other triangles fractally" class="fit-image" src="https://ztoz.blog/posts/postscript-code/Sierpinski-PS.png" /&gt;
  
  &lt;figcaption&gt;Figure A: Sierpinski Triangle produced via PostScript code&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;The PostScript code requires less than 700 bytes to describe the image, which can be scaled and translated arbitrarily or, with some tweaking of the parameters, rendered to a greater depth. In comparison, the compressed PNG requires 15,000 bytes of storage (even with space optimization) and a Scalable Vector Graphics representation 2,800 bytes (or 560 bytes gzipped). The SVG representation builds the image out of vector-based triangles, which can be arbitrarily scaled and translated, but SVG lacks the ability to increase or decrease the depth of the fractal.&lt;/p&gt;
&lt;h3 id="study-limitations"&gt;Study Limitations&lt;/h3&gt;
&lt;p&gt;Although all historical studies are limited by the unavailability of certain artifacts and the distance of time, there are a few limitations particular to this study. First, Adobe has chosen not to release certain files that are still covered by trade secrets. The distribution is missing the files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;except.h&lt;/li&gt;
&lt;li&gt;bezier.c&lt;/li&gt;
&lt;li&gt;curvefit.c&lt;/li&gt;
&lt;li&gt;gray.c and .h&lt;/li&gt;
&lt;li&gt;reducer.c and .h&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The effect of these omissions is that the software will not build &amp;ldquo;as-is&amp;rdquo;. By examining references in existing code, we counted 19 functions or macros that are referenced in the distributed code but are missing implementations.&lt;/p&gt;
&lt;p&gt;Secondly, Adobe and CHM have released the code under a custom &lt;a href="https://22740101.fs1.hubspotusercontent-na1.net/hubfs/22740101/chm-eula-postscript-agreement.pdf"&gt;EULA&lt;/a&gt; that prohibits various activities. In personal correspondence with the CHM, we have received authorization to display sections of the source code with our comments as they consider public scholarship to fall under educational use.&lt;/p&gt;
&lt;h2 id="timeline"&gt;Timeline&lt;/h2&gt;
&lt;h3 id="contextual-timeline"&gt;Contextual Timeline&lt;/h3&gt;
&lt;p style="text-align: center;"&gt;&lt;strong&gt;Table 1: Historical Context Timeline&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Date&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1965&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;University of Utah&amp;rsquo;s establishes graphics center of excellence&lt;/strong&gt; Using a grant from ARPA, the university launches a &lt;a href="https://ieee-region6.org/2023/utah-computer-graphics-and-visualization-milestone/"&gt;broad research program&lt;/a&gt; into computer graphics.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1971?&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Harbor Pilot Simulator&lt;/strong&gt;. Evans and Sutherland (E&amp;amp;S) awarded contract to build a harbor pilot simulator for the New York Maritime Academy. To populate the 3D database, Warnock&amp;rsquo;s team invented a stack-based language that created the geometries and textures.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1971&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Xerox invents the laser printer&lt;/strong&gt;. Laser printer technology enabled high quality, high throughput printing. The first commercial printer came in 1976 (at least one that supported multiple fonts), IBM&amp;rsquo;s 3800, and soon thereafter by offerings from Xerox and Canon.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1974&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Xerox develops Press&lt;/strong&gt;. The first successful page description format is developed at PARC, incorporating lessons from two previous systems. Press supports text, with multiple fonts, images, and filled objects to be incorporated on the same page. Over 200,000 documents are created in Press by 1983.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1977&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;E&amp;amp;S Design System&lt;/strong&gt;. E&amp;amp;S evolve the language from the Harbor Pilot simulator  into a CAD system.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1977&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;TeX Project Launched&lt;/strong&gt;. In May, Donald Knuth writes a memo describing the core features of TeX, a new typesetting system. This project would see the first major release in 1982 and the design frozen with release 3.0 in 1990.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1978&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;JaM invented at Xerox PARC&lt;/strong&gt;. The John and Martin language is used to experiment with graphical models. This language was inspired by the E&amp;amp;S Design System language and the graphical model inspired PostScript&amp;rsquo;s model.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1981&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Interpress started&lt;/strong&gt;. Xerox starts work on a successor to Press.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1981-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Command Language Patent filed&lt;/strong&gt;. John Gaffney files patent for a &amp;ldquo;Command language system for interactive computer&amp;rdquo; based on his work for the Evans and Sutherland Design System.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1982-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Interpress 82&lt;/strong&gt;. Specifications for &amp;ldquo;version 1&amp;rdquo; of Interpress, Xerox&amp;rsquo;s language and page description system to replace Press are published for internal consumption.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1982-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Adobe Founded&lt;/strong&gt;. Chuck Geschke and John Warnock found Adobe.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-04&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;DEC interested in license agreement&lt;/strong&gt; Gordon Bell of DEC meets with team and expresses interest in PostScript as a printer protocol. DEC eventually licenses the technology.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-05&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Steve Jobs visit&lt;/strong&gt;. Steve Jobs meets with Adobe and expresses interest in PostScript as a printer protocol for the upcoming Apple laser printer.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Adobe signs license agreement with Apple&lt;/strong&gt; Adobe will provide software for the LaserWriter and ensure PostScript&amp;rsquo;s quality at 300 dpi.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1984&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;PostScript released&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1984&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Linotronic 300 Imagesetter released&lt;/strong&gt;. First commercial printer to support PostScript.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1984-04&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Xerox announces documentation for Interpress available&lt;/strong&gt;. Version 2.1 of the Interpress Standard is first released externally.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1985&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;PostScript manuals published&lt;/strong&gt; The &amp;ldquo;Red&amp;rdquo; book and &amp;ldquo;Green&amp;rdquo; book provide a high-quality reference manual and tutorial for PostScript. The reference manual states that it covers PostScript version 23.0.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1985&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Apple LaserWriter, Aldus Pagemaker 1.0 released&lt;/strong&gt;. Creatives could now generate documents competitive to commercial printers in quality and without specialized programming or typesetting skills.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1991&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;PostScript Level 2 released&lt;/strong&gt;. Expanded the capabilities and reliability of the language, but saw limited commercial impact as Level 1 satisfied most needs.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1993&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;PDF 1.0 released&lt;/strong&gt;. Increased page throughput and reduced variability by sharply limiting the programming language power.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1997&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;PostScript Level 3 released&lt;/strong&gt;. Last release of PostScript.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="development-timeline"&gt;Development Timeline&lt;/h3&gt;
&lt;p&gt;Based on the file edit history captured in comments within each file, we can attempt to reconstitute the order of development. Table 2, below, lists the creation date (year and month) for each file (denoted in &lt;em&gt;italics&lt;/em&gt;) and dates that the file was edited (denoted in normal typeface). The comments do not state what was edited, just the author and date. A file may be listed multiple times in a month depending on the edit history.&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;strong&gt;Table 2: Development Timeline&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Date&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Files Created or Edited&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;STbuild.c&lt;/em&gt;, &lt;em&gt;errors.h&lt;/em&gt;, &lt;em&gt;globals.h&lt;/em&gt;, &lt;em&gt;postscript.h&lt;/em&gt;, &lt;em&gt;procs.h&lt;/em&gt;, &lt;em&gt;scanner.c&lt;/em&gt;, &lt;em&gt;types.h&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;array.c&lt;/em&gt;, &lt;em&gt;control.c&lt;/em&gt;, &lt;em&gt;debug.c&lt;/em&gt;, &lt;em&gt;dict.c&lt;/em&gt;, &lt;em&gt;error.c&lt;/em&gt;, &lt;em&gt;exec.c&lt;/em&gt;, &lt;em&gt;math.c&lt;/em&gt;, &lt;em&gt;name.c&lt;/em&gt;, &lt;em&gt;stack.c&lt;/em&gt;, &lt;em&gt;stream.c&lt;/em&gt;, &lt;em&gt;string.c&lt;/em&gt;, &lt;em&gt;type.c&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-03&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;matrix.c&lt;/em&gt;, error.c, errors.h, globals.h, math.c, procs.h, stream.c, types.h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-04&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;banddevice.c&lt;/em&gt;, &lt;em&gt;user.c&lt;/em&gt;, &lt;em&gt;xylock.c&lt;/em&gt;, banddevice.c, matrix.c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-05&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;fontcache.c&lt;/em&gt;, &lt;em&gt;graphics.c&lt;/em&gt;, &lt;em&gt;graphics.h&lt;/em&gt;, &lt;em&gt;inputdevice.c&lt;/em&gt;, &lt;em&gt;memdevice.c&lt;/em&gt;, &lt;em&gt;nulldevice.c&lt;/em&gt;, &lt;em&gt;path.c&lt;/em&gt;, &lt;em&gt;stroke.c&lt;/em&gt;, dict.c, fontcache.c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-06&lt;/td&gt;
&lt;td style="text-align: left;"&gt;fontcache.c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-07&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;fontdisk.c&lt;/em&gt;, &lt;em&gt;fonts.h&lt;/em&gt;, &lt;em&gt;fontshow.c&lt;/em&gt;, math.c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-08&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;graphicspriv.h&lt;/em&gt;, &lt;em&gt;sundev.c&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-09&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;framedevice.c&lt;/em&gt;, &lt;em&gt;framedevice.h&lt;/em&gt;, &lt;em&gt;image.c&lt;/em&gt;, &lt;em&gt;versatecdev.c&lt;/em&gt;, versatecdev.c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-10&lt;/td&gt;
&lt;td style="text-align: left;"&gt;fontdisk.c, fontshow.c, framedevice.h, sundev.c, user.c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-11&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;em&gt;device.c&lt;/em&gt;, &lt;em&gt;font.c&lt;/em&gt;, &lt;em&gt;fontbuild.c&lt;/em&gt;, &lt;em&gt;fontcrypt.c&lt;/em&gt;, &lt;em&gt;graphpak.c&lt;/em&gt;, &lt;em&gt;unix.c&lt;/em&gt;,  &lt;em&gt;vm.c&lt;/em&gt;, error.c, errors.h, framedevice.h, graphics.c, graphpak.c, image.c, inputdevice.c, memdevice.c, nulldevice.c, stroke.c, types.h&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1983-12&lt;/td&gt;
&lt;td style="text-align: left;"&gt;banddevice.c, banddevice.c, device.c, font.c, fontcrypt.c, globals.h, inputdevice.c, matrix.c, matrix.c, memdevice.c, nulldevice.c, postscript.h, sundev.c, unix.c, user.c, versatecdev.c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1984-01&lt;/td&gt;
&lt;td style="text-align: left;"&gt;STbuild.c, font.c, graphics.h, graphics.h, math.c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;1984-02&lt;/td&gt;
&lt;td style="text-align: left;"&gt;array.c, control.c, debug.c, device.c, dict.c, exec.c, fontbuild.c, fontbuild.c, fontcache.c, fontdisk.c, fonts.h, fontshow.c, framedevice.c, framedevice.c, graphics.c, graphicspriv.h, graphicspriv.h, image.c, name.c, path.c, path.c, procs.h, scanner.c, stack.c, stream.c, string.c, stroke.c, type.c, types.h, vm.c, xylock.c&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;This timeline suggests that the major language components and processing loop for PostScript were completed in the first three months, or January to March 1983. The team then began focusing on the graphics engine, including printing to their borrowed LN01 laser printer, as denoted by the work on the &lt;code&gt;banddevice&lt;/code&gt; and &lt;code&gt;xylock&lt;/code&gt; files, as well as &lt;code&gt;graphics&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt;, and &lt;code&gt;stroke&lt;/code&gt;. This effort took most of April and May. Work on fonts, at least initial support, started in May and was the predominant effort in June and July. In August, September, and October, the team started rendering images on the Sun-1 framebuffer, along with additional improvements to fonts. November sees the last big fleshing out of the design, with additional work on fonts and graphics, refinement of interfaces to input and output devices, and virtual memory support for running within restricted devices. The last three months, December to February 1984, see very diffuse efforts, implying integration changes, performance enhancements, and bug fixes.&lt;/p&gt;
&lt;h2 id="developers-and-their-influences"&gt;Developers and their Influences&lt;/h2&gt;
&lt;p&gt;In the forward to the 1985 &lt;em&gt;PostScript Language Reference Manual&lt;/em&gt;, John Warnock attributes the beginning of the PostScript design to John Gaffney and the Design System, developed at Evans and Sutherland. Furthermore, he notes the contributions of JaM, a language he developed with Martin Newell at Xerox. PostScript came from a rich intellectual pedigree and benefited from prior art.&lt;/p&gt;
&lt;h3 id="co-founders"&gt;Co-Founders&lt;/h3&gt;
&lt;p&gt;Charles Geschke and John Warnock were the two co-founders of Adobe. Both were &amp;ldquo;technical&amp;rdquo; founders and brought relevant expertise to the design and implementation of PostScript.&lt;/p&gt;
&lt;h4 id="charles-chuck-geschke"&gt;Charles (Chuck) Geschke&lt;/h4&gt;
&lt;p&gt;Charles (Chuck) Geschke was awarded a PhD in Computer Science from Carnegie Mellon University. His thesis was titled &amp;ldquo;Global Program Optimizations&amp;rdquo; and his advisor was William Wulf.&lt;/p&gt;
&lt;p&gt;Joining Xerox PARC in 1972, he created the Imaging Sciences Laboratory in 1978, and hired John Warnock soon after. While at PARC, Geschke was involved in the design and implementation of a Mesa language compiler. &lt;a href="https://www.softwarepreservation.org/projects/mesa/"&gt;Mesa&lt;/a&gt; features a module system and a strong type system. Although Mesa was not used to implement PostScript (the language was proprietary to Xerox), Mesa-isms can be seen in the PostScript code as described in the later section, &amp;ldquo;Programming Language and Dialect&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;The majority of changes in the version 1.0 source code are attributed to Geschke.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.computerhistory.org/blog/remembering-charles-m-geschke/"&gt;CHM Obituary&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="john-warnock"&gt;John Warnock&lt;/h4&gt;
&lt;p&gt;John Warnock was awarded his PhD from the University of Utah in 1972 for the thesis &amp;ldquo;A hidden surface algorithm for computer generated halftone pictures&amp;rdquo;. His advisor was David Evans who, along with fellow committee member Ivan Sutherland, hired him into Evans and Sutherland. This was a heady period as the University of Utah had been recently established as a national center for computer graphics research by DARPA and Evans and Sutherland were pioneering many graphical and computer simulation techniques.&lt;/p&gt;
&lt;p&gt;Warnock stayed at Evans and Sutherland for six years. One of the earliest influences on PostScript&amp;rsquo;s design was the Harbor Pilot Simulator. Warnock&amp;rsquo;s team was responsible for creating the 3D environment for the harbor. A member of Warnock&amp;rsquo;s team, John Gaffney, developed a stack-based language that could populate the database. This language later developed into The Design System (1976).&lt;/p&gt;
&lt;p&gt;In 1978, Warnock joined Xerox PARC and Geschke&amp;rsquo;s team. Along with Martin Newell, Warnock developed JaM, a programming language based on The Design System. JaM influenced Interpress, a programming language and page description format based on learnings from Xerox&amp;rsquo;s Press system. The paper (Warnock and Wyatt 1982) describes a graphical model used in some Xerox efforts that later is the basis for PostScript&amp;rsquo;s graphical model.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.computerhistory.org/blog/in-memoriam-john-warnock/"&gt;CHM Obituary&lt;/a&gt;&lt;/p&gt;
&lt;h3 id="contributors-to-version-10"&gt;Contributors to Version 1.0&lt;/h3&gt;
&lt;p&gt;Using the changelog attributions, the list of developers (excluding the two co-founders) for version 1.0 were Tom Boynton, Doug Brotz, and Andrew Shore. In (Warnock and Geschke 2019)&amp;rsquo;s description of the founding of Adobe, the team listing includes Tom Boynton and Dan Putnam (both described as electronic engineers), Doug Brotz, and Bill Paxton, but drops Shore. ACM&amp;rsquo;s Software System Award for PostScript includes the two co-founders and Brotz, Paxton, and Edward Taft. Paxton joined Adobe in 1983 and Taft in February 1984, both early enough to make contributions to PostScript but missed contributing to the 1.0 source code.&lt;/p&gt;
&lt;h4 id="tom-boynton"&gt;Tom Boynton&lt;/h4&gt;
&lt;p&gt;Tom Bonyton is credited with the original versions of banddevice.c and math.c, but edited other files as well. A band device does not support representing the page as a full array of pixels, but rather requires being fed a sequence of bands with one being consumed while the program produces the other. DEC had lent Adobe an early laser printer (LN01, based on the Xerox 2700) which, since it was severely limited in memory, used two band buffers for printing. Boynton worked at Xerox PARC previously to Adobe, but we were unable to find additional biographical details. It is likely that when Adobe decided to be a software-only firm, the electronic engineers left for other companies.&lt;/p&gt;
&lt;h4 id="doug-brotz"&gt;Doug Brotz&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://computerhistory.org/profile/doug-brotz/"&gt;Doug Brotz&lt;/a&gt; was awarded a PhD in Computer Science from Stanford University. His thesis was on &lt;a href="https://searchworks.stanford.edu/view/810229"&gt;automated theorem proving&lt;/a&gt;. After a period of time at the University of Arizona, he joined Xerox in 1977. This soon led him to PARC where he worked on the &lt;a href="https://www.youtube.com/watch?v=tngrLvyiNEI"&gt;Laurel email client&lt;/a&gt;. Laurel was implemented in the &lt;a href="https://xeroxalto.computerhistory.org/xerox_alto_file_system_archive.html#Mesa"&gt;Mesa programming language&lt;/a&gt;. During the development of Laurel, Brotz met Warnock when Warnock recommended using Laurel&amp;rsquo;s plugin capability to integrate with JaM and allow the sending of graphics in email messages.&lt;/p&gt;
&lt;p&gt;He joined Adobe in March 1983 and made many contributions to PostScript, particularly in graphics and font-handling. His earliest contributions date to May 1983. Based on the changelog, he is the second most prolific author behind Geschke.&lt;/p&gt;
&lt;p&gt;For someone who made many fundamental contributions to PostScript, Brotz surprisingly lacked any background in graphics programming. He recounts his involvement with the reducer algorithm in (Perry 1988):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“About a week after I had joined Adobe in 1983,” Brotz recalls, “John Warnock mentioned this rather important algorithm that had to be written. And I, with no graphics background, volunteered. Several months later, older and wiser, I realized it truly was one of the world’s hardest problems.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;But Brotz did not give up, and he says, “We have now an exactly correct reducer algorithm. It is the heart of the graphics system in PostScript.” And a tally Brotz keeps reveals that no bugs have been discovered in the Reducer in more than two years.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="andrew-andy-shore"&gt;Andrew (Andy) Shore&lt;/h4&gt;
&lt;p&gt;We were unable to find many details about Andrew Shore. The changelog indicates he wrote the initial version of the unix.c file and made edits to fontbuild.c. He was active on the fa.Laser Lovers USENET channel in 1984 and 1985 using an Adobe email address. Adobe celebrated his presence in a &lt;a href="https://blog.adobe.com/en/publish/2021/06/08/glow-up-show-up-lift-up"&gt;2021 blog post&lt;/a&gt;, but the posting yields few biographical details.&lt;/p&gt;
&lt;p&gt;However, he is anecdotally remembered via &amp;ldquo;Andy&amp;rsquo;s Stupid Input Device.&amp;rdquo; (Perry 1988) recounts how PostScript came to support scanners and other graphic input devices:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;As originally conceived, PostScript was to have been independent of the output, but not the input, device. Warnock had thought that PostScript, to take in scanned images, would need to contain information about a wide range of optical scanners. But Brotz, after programming the parameters of just two of many scanner types, realized that the task was not only horrendous and repetitive but ate up a lot of memory.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Andy Shore, an Adobe computer scientist, overheard him complaining one day and suggested writing a PostScript procedure that would pretend that it was an input device and spit out the image information in a standard format, regardless of the characteristics of the actual standard. Brotz did not think it would work and “Warnock promptly labeled it ‘Andy’s Stupid Input Device.’”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Still, Brotz thought it might be helpful for generating test patterns, and when he implemented it, “it turned out that Andy’s Stupid Input Device was the lowest common denominator and all the special-case code could disappear.” Problems arise only when the image data has been compressed for transmission or storage; the programmer then has to insert a routine to decompress the data before it is handed to the image algorithm.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This anecdote demonstrates two aspects of PostScript; first, the interface designed around a &amp;ldquo;lowest common denominator&amp;rdquo; of functionality facilitated a wide variety of devices to plug-in to the system, greatly increasing its value. Furthermore, supporting compressed images was a matter of composing procedures, a matter very straight-forward using the stacks and dictionaries within the language.&lt;/p&gt;
&lt;h3 id="the-design-system"&gt;The Design System&lt;/h3&gt;
&lt;p&gt;John Gaffney was a member of Warnock&amp;rsquo;s Evans and Sutherland team when they were building  the Harbor Pilot Simulator. The project was behind schedule and they needed to rapidly build a 3D model of the harbor. Gaffney built a language that programmatically constructed database elements. Coupled with a menu-driven user interface, the team finished the database in time. Later, the language was expanded into the Design System. Gaffney&amp;rsquo;s 1981 patent &amp;ldquo;Command language system for interactive computer&amp;rdquo; describes &amp;ldquo;The Design System&amp;rdquo;&amp;rsquo;s language thusly:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A command language system is disclosed wherein memory stacks register specific definitions for generic names, which definitions are appropriately selected in timely response to a name, on the basis of stack arrangement. A structure is included for searching the stack in order and detecting the first definition for a name of current interest. Thus, the stack is used to define the order of the name searching. As a consequence, in the interpretation of command languages, the user is given control over the context in which the names are executed. Specifically, a command program will behave according to the definitions of the commands in a current context. The system further includes structure for deleting definitions from the stack which have been used and for sensing the bottom of the stack as a function of control.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Put simply, the language defines stacks of dictionaries under the programmer&amp;rsquo;s control. When a name is looked up, the stack is searched in order. Thus, a programmer can replace or augment functionality by adding or deleting entries within a dictionary, creating new dictionaries, and changing the order of dictionaries within the stack. Along with an interactive interface and a defined entity model, the earlier Harbor Pilot Simulator implementation facilitated rapid creation of a 3D database.&lt;/p&gt;
&lt;p&gt;Only one copy of The Design System was sold and, when the director died, the project ended (Perry 1988). However, the key aspects of the Design System were identified in (Reid 1988) as the key aspects of the PostScript language.&lt;/p&gt;
&lt;h3 id="xerox"&gt;Xerox&lt;/h3&gt;
&lt;p&gt;In the book &lt;em&gt;Introduction to Interpress&lt;/em&gt; (Sproull and Reid 1983, 221-223), the authors describe the lessons learned over multiple iterations of publishing tools at Xerox. Although the Adobe developers may have disagreed with some of these lessons, they all, especially the founders, would have been aware of them.&lt;/p&gt;
&lt;p&gt;Shortly after PARC was founded in 1970, one of the employees developed the &lt;code&gt;listing&lt;/code&gt; program which translated ASCII documents into a raster image for a printer. Users could insert inline escape sequences to control font selection, insert images, and control simple formatting choices like subscripts and superscripts. However, the program made many stylistic choices for the document which often did not comply with the user&amp;rsquo;s wishes. The developers learned that style and layout decisions should not be coupled in the same layer with rasterization. For instance, PostScript does not include logic for text wrapping; those decisions are handled at a higher level.&lt;/p&gt;
&lt;p&gt;PARC engineers also developed systems that were deeply tied to a single printer&amp;rsquo;s capabilities. Users sent page descriptions tailored to that printer. Although a user could maximize the performance of a given printer, this approach required too much from users and was considered to be too low-level.&lt;/p&gt;
&lt;p&gt;Based on these learnings, PARC engineers designed Press in 1974. They designed Press to be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Device independent&lt;/li&gt;
&lt;li&gt;Single file containing embedded text and graphics&lt;/li&gt;
&lt;li&gt;Font libraries were delegated to the printer&lt;/li&gt;
&lt;li&gt;Un-opinionated by making no formatting decisions&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Press was very successful. The authors state it was used in over 200,000 documents, many created by non-technical employees.&lt;/p&gt;
&lt;p&gt;In 1981, limitations in Press led to a new design, Interpress. Interpress differentiated itself from Press by:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Press is a data structure while Interpress (and PostScript) is a program&lt;/li&gt;
&lt;li&gt;Press has a single coordinate system rather than supporting multiple coordinate systems. PostScript and Interpress could translate and scale multiple pages onto a single page programmatically.&lt;/li&gt;
&lt;li&gt;Press&amp;rsquo;s graphic model only supports filled objects and pixel arrays&lt;/li&gt;
&lt;li&gt;Press lacks a &lt;code&gt;correct&lt;/code&gt; facility for correcting font approximations&lt;/li&gt;
&lt;li&gt;Press files contain both printer data and application information&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PostScript and Interpress were very similar in the first three bullet points. PostScript approached fonts differently than Interpress and thus did not need a &lt;code&gt;correct&lt;/code&gt; facility.&lt;/p&gt;
&lt;p&gt;With the benefit of hindsight of the success of PDF, we may question why Interpress and Postscript both chose to be a program rather than just a page description. In (Warnock 2012), the author lists the advantages and disadvantages of program as a representation:&lt;/p&gt;
&lt;p&gt;Disadvantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Programs might never stop&lt;/li&gt;
&lt;li&gt;Indeterminate number of pages can be produced&lt;/li&gt;
&lt;li&gt;Program dictates order of pages produced&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Operators can be redefined, so programs can fix or extend the language&lt;/li&gt;
&lt;li&gt;Programs can interpret other printer protocols and emulate them&lt;/li&gt;
&lt;li&gt;Character encodings (e.g. Japanese, Farsi) can be implemented programmatically&lt;/li&gt;
&lt;li&gt;Subroutines can efficiently implement sub-components on pages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The second advantage was used for the Apple LaserWriter as a PostScript program could read and translate Apple&amp;rsquo;s QuickDraw graphical model into the PostScript graphical model, saving time and effort versus a separate translation program.&lt;/p&gt;
&lt;h2 id="development-and-test-environment"&gt;Development and Test Environment&lt;/h2&gt;
&lt;p&gt;Adobe engineers developed on a leased VAX 750 running Berkeley Unix. For testing, the team used a Sun-1 workstation that included a primitive frame buffer and a LN01 laser printer borrowed from DEC (Perry 1988). The LN01 was a rebranded Xerox 2700.&lt;/p&gt;
&lt;p&gt;The printer was capable of 300 dpi and 12 pages/minute. It used an Intel 8086 processor with either 64kb or 256kb of RAM and used two band buffers to enable reading additional data while printing the contents of the other buffer (Xerox 2022).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Roket: In the file &lt;code&gt;framebuffer.c&lt;/code&gt;, there is a function called &lt;code&gt;psFrameToRoket&lt;/code&gt; which is only enabled for Sun builds. The function outputs a bitmap to &lt;code&gt;/dev/roket&lt;/code&gt; and uses ioctl to adjust values such as width and height. We have been unable to determine what hardware or interface is being used here as roket isn&amp;rsquo;t a term in the Sun-1 user manuals.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="design-and-source-code"&gt;Design and Source Code&lt;/h2&gt;
&lt;p&gt;The following sections analyze the version 1.0 source code artifacts.&lt;/p&gt;
&lt;h3 id="physical-form"&gt;Physical Form&lt;/h3&gt;
&lt;p&gt;Per David A. Wheeler&amp;rsquo;s &lt;code&gt;SLOCCount&lt;/code&gt;, the distribution contains approximately 12,000 lines of C code (.c and .h files) and a makefile. Per &lt;code&gt;cloc&lt;/code&gt;, there are 1,171 lines of comments. However, since each file contains a commented copyright notice and edit history, there are less than 600 &amp;ldquo;true&amp;rdquo; comments in the code.&lt;/p&gt;
&lt;p&gt;The makefile organizes the source files into five groups: kernel, graphics, device, fonts, and user. The kernel files implement the basic language and core operators, as well as the virtual memory subsystem. The graphics files implement paths, strokes, matrices and other graphic primitives, and the reducer. Device files handle input and output devices. Font files handle reading, caching, and rendering fonts. User files handle some hosting concerns on Unix machines.&lt;/p&gt;
&lt;p&gt;Although we are missing some files, we can count lines of code per file group (Table 3) to get a sense of the complexity of the groups, at least once they are rendered to code. Per comments from the co-founders, font handling and graphics reduction were considered open problems as they started implementation. Although they had experience with both problems at Interpress, the co-founders considered the previous solutions as having insufficient quality and flexibility. Legally, the co-founders also wanted designs that were free from Xerox intellectual property. Adobe licensed the Design System, so the &amp;ldquo;kernel&amp;rdquo; was legally protected.&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;strong&gt;Table 3: Lines of Code by File Group&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;File Group&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Lines of Code&lt;/th&gt;
&lt;th style="text-align: right;"&gt;LOC Percent&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Kernel&lt;/td&gt;
&lt;td style="text-align: right;"&gt;5564&lt;/td&gt;
&lt;td style="text-align: right;"&gt;46&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Graphics&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2410&lt;/td&gt;
&lt;td style="text-align: right;"&gt;20&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Missing gray and reducer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Fonts&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2014&lt;/td&gt;
&lt;td style="text-align: right;"&gt;17&lt;/td&gt;
&lt;td style="text-align: left;"&gt;Missing bezier and curvefit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Device&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1647&lt;/td&gt;
&lt;td style="text-align: right;"&gt;14&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;User&lt;/td&gt;
&lt;td style="text-align: right;"&gt;123&lt;/td&gt;
&lt;td style="text-align: right;"&gt;1&lt;/td&gt;
&lt;td style="text-align: left;"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="oddball-files"&gt;Oddball Files&lt;/h3&gt;
&lt;p&gt;The distribution includes four &amp;ldquo;oddball&amp;rdquo; files or files that do not seem to properly belong to the software. These files are &lt;code&gt;begin.c&lt;/code&gt;, &lt;code&gt;end.c&lt;/code&gt;, &lt;code&gt;bug.c&lt;/code&gt;, and &lt;code&gt;padPS.c&lt;/code&gt;. None include the customary copyright header or comments describing authorship or edit history. None are included in the Makefile. Due to their nature, we usually excluded them from code analysis.&lt;/p&gt;
&lt;p&gt;The pair &lt;code&gt;begin.c&lt;/code&gt; and &lt;code&gt;end.c&lt;/code&gt; each define a single function, BEGIN and END respectively, that simply sets the local variable &lt;code&gt;i&lt;/code&gt; to zero. Based on code in &lt;code&gt;control.c&lt;/code&gt;, these two functions are only included if the release flag is not set and are used as part of some performance monitoring function.&lt;/p&gt;
&lt;p&gt;The standalone program &lt;code&gt;bug.c&lt;/code&gt; tests that the value of a local unsigned short is equal to an extracted short from a &lt;code&gt;struct&lt;/code&gt; bitfield. The PostScript code uses struct bitfields in various places, so this was likely used to either test a potential compiler bug or to verify some behavior of the C language.&lt;/p&gt;
&lt;p&gt;Another standalone program, &lt;code&gt;padPS.c&lt;/code&gt;, reads a hexadecimal number from the command line arguments and two decimal numbers from &lt;code&gt;stdin&lt;/code&gt;. The program then performs a short calculation of a padding value and either prints out the padding value or an error message. Since the calculation does not use the second number decimal number, this was likely some ad-hoc program used during development.&lt;/p&gt;
&lt;p&gt;There are two programs that have their own entry points (&lt;code&gt;main&lt;/code&gt; functions) and have the standard copyright headers, but are not part of the makefile. These are &lt;code&gt;STbuild.c&lt;/code&gt; and &lt;code&gt;fontcrypt.c&lt;/code&gt;. The purpose of STBuild.c is to produce scan tables incorporated in &lt;code&gt;scanner.c&lt;/code&gt;. Because the output tables are incorporated into the code, rather than being generated every build, the code served a development purpose but was not included in the PostScript program itself. The &lt;code&gt;fontcrypt.c&lt;/code&gt; programs takes a file, presumably a font file, and encrypts it by &lt;code&gt;xor&lt;/code&gt;-ing each byte with a pseudo-random byte (via a linear congruential generator). This process can be reversed in &lt;code&gt;scanner.c&lt;/code&gt; or &lt;code&gt;fontbuild.c&lt;/code&gt; which can reproduce the random byte stream. The encryption method (and constants) were updated by the time the Font 1 Specification was released (Adobe Systems Incorporated 1993).&lt;/p&gt;
&lt;h3 id="architecture"&gt;Architecture&lt;/h3&gt;
&lt;p&gt;John Warnock advocated for a Unix-like approach to the PostScript design. Thus, PostScript&amp;rsquo;s design mirrors a Unix filter in that it reads from stdin and writes to either stdout or stderr. Although PostScript is primarily used to interact with printers, the design does not include features related to queuing multiple jobs or keeping track of spend. Instead, the assumption is that an intermediary program would handle those jobs. This act of composing functionality through multiple programs is also part of the Unix philosophy. Figure B describes the deployment architecture.&lt;/p&gt;



&lt;figure&gt;
  
  &lt;img alt="PostScript daemon reads via stdin a program (with optional queuing program intermediary) and outputs to a raster image processor which leads to a printer or display" class="fit-image" src="https://ztoz.blog/posts/postscript-code/PostScript-deployment.png" /&gt;
  
  &lt;figcaption&gt;Figure B: PostScript Deployment Components and Flow&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;Since there is a large cost to start-up (primarily due to loading fonts), PostScript is designed to initialize once and then cheaply reset to a known state after every job. (Since many implementations printed a test page on start-up, minimizing the number of start-ups also helped save ink and paper.) The virtual memory storage is a copy of the memory or global state of the program.&lt;/p&gt;
&lt;p&gt;On the right-side of Figure B is the raster image processor (RIP) which translates the rasterized pages into the appropriate final representation for the output device. The 1.0 source code does not include the acronym RIP nor includes the full term. However, RIPs were part of the architecture by the time the first commercial printers became available. (Industry use of the term RIP is confusing. A RIP could be an embeddable component which includes PostScript, or RIP could be equivalent to a PostScript output device.)&lt;/p&gt;
&lt;p&gt;With simple streaming inputs and outputs, PostScript could be embedded within other systems flexibly and remain agnostic about client interfaces. As a streaming system, PostScript could operate in both a batched and interactive manner. This design was attractive to both creatives and printer manufacturers. For the former, they could target a single language for output and then take that file to any printer (or similar device). Manufacturers could add special sauce at the RIP layer and continue to improve quality and throughput on the output-side without requiring changes to the input. Printer services could embed PostScript into their printing orchestration services and anyone could print, at whatever quality they could afford.&lt;/p&gt;
&lt;h3 id="programming-language-and-dialect"&gt;Programming Language and Dialect&lt;/h3&gt;
&lt;p&gt;The source code is written in K&amp;amp;R C (the first Standard Draft was released in 1985 and the first standard followed in 1989). However, the developers used the preprocessor and typedefs to make the language more Mesa-like. Mesa was a systems programming language at Xerox and Charles Geschke had been deeply involved in the design of the language and the compiler. The &lt;code&gt;PrimeSize&lt;/code&gt; function from &lt;code&gt;dict.c&lt;/code&gt; serves as an example of this dialect:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c"&gt;private cardinal &lt;span style="color: #a6e22e;"&gt;PrimeSize&lt;/span&gt;(s)
	cardinal s;
{
  cardinal i, size &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; (s &lt;span style="color: #f92672;"&gt;&amp;lt;=&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;500&lt;/span&gt;)&lt;span style="color: #f92672;"&gt;?&lt;/span&gt; (s &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; s&lt;span style="color: #f92672;"&gt;/&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt; &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;) &lt;span style="color: #f92672;"&gt;:&lt;/span&gt; (s &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; s&lt;span style="color: #f92672;"&gt;/&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;5&lt;/span&gt; &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;);
  &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (MAXdictLength &lt;span style="color: #f92672;"&gt;!=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;2000&lt;/span&gt;)
    BUG(&lt;span style="color: #e6db74;"&gt;"This version not prepared for dictionaries over 2000 elements!"&lt;/span&gt;);
  &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (s &lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt; MAXdictLength) ERROR(limitcheck);
  &lt;span style="color: #66d9ef;"&gt;switch&lt;/span&gt; (s) {
    &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;;
    &lt;span style="color: #75715e;"&gt;/* [...] */&lt;/span&gt;
    &lt;span style="color: #66d9ef;"&gt;case&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;10&lt;/span&gt;&lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;13&lt;/span&gt;;
    endswitch}
  i &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;;
  until ((size &lt;span style="color: #f92672;"&gt;&amp;gt;=&lt;/span&gt; primes[i])&lt;span style="color: #f92672;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;(size &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt; primes[i&lt;span style="color: #f92672;"&gt;+&lt;/span&gt;&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;])) {i&lt;span style="color: #f92672;"&gt;++&lt;/span&gt;;}
  &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; primes[i];
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mesa featured a strong module system and the developers wished to capture the documentary value of modules, even if the compiler lacked the semantics. The developers marked functions as &lt;code&gt;private&lt;/code&gt; or &lt;code&gt;public&lt;/code&gt; and these mapped to C&amp;rsquo;s &lt;code&gt;static&lt;/code&gt; or default scope. If a function did not return a value, they used &lt;code&gt;procedure&lt;/code&gt; as the return type. The developers typedef the basic types to their Mesa names, such as &lt;code&gt;cardinal&lt;/code&gt; for &lt;code&gt;unsigned short int&lt;/code&gt;, &lt;code&gt;real&lt;/code&gt; for &lt;code&gt;float&lt;/code&gt;, and &lt;code&gt;character&lt;/code&gt; to &lt;code&gt;unsigned char&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Also similar to Mesa, the &lt;code&gt;BUG&lt;/code&gt; and &lt;code&gt;ERROR&lt;/code&gt; procedures raised signals. The &lt;code&gt;endswitch&lt;/code&gt; macro added an empty default case while the &lt;code&gt;until&lt;/code&gt; macro replaced the clause with a &lt;code&gt;while&lt;/code&gt; loop and negated the expression.&lt;/p&gt;
&lt;h3 id="historical-constraints"&gt;Historical Constraints&lt;/h3&gt;
&lt;p&gt;The rasterization process requires encoding the color value (which admittedly fit within a single bit for the initial devices) for each pixel or dot on a page. The storage requirements, if the entire page was rasterized at once, were significant at the time (Table 4). Thus, the design of PostScript had to accommodate limited memory capacity. Indeed, their test printer, the LN01 / Xerox 2700, could only hold two sets of 32 scan lines of 300 dpi at a time. Although the price of memory soon collapsed (Figure C) in 1984, prices were relatively stable in 1982 to 1984 during the development of PostScript. Without the collapse in prices, laser printers would have not been commercially viable and the desktop publishing revolution would have been postponed.&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;strong&gt;Table 4: Storage Requirements for Rasterization on Extant Devices&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Output Device&lt;/th&gt;
&lt;th style="text-align: left;"&gt;Resolution&lt;/th&gt;
&lt;th style="text-align: right;"&gt;DPI&lt;/th&gt;
&lt;th style="text-align: right;"&gt;Bytes to Raster One Page&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Macintosh 128k&lt;/td&gt;
&lt;td style="text-align: left;"&gt;512x342&lt;/td&gt;
&lt;td style="text-align: right;"&gt;72&lt;/td&gt;
&lt;td style="text-align: right;"&gt;21,888&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;LaserWriter&lt;/td&gt;
&lt;td style="text-align: left;"&gt;7.66&amp;quot;x10.16&amp;quot;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;300&lt;/td&gt;
&lt;td style="text-align: right;"&gt;875,475&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;Linotronic 300&lt;/td&gt;
&lt;td style="text-align: left;"&gt;8.5&amp;quot;x11&amp;quot;&lt;/td&gt;
&lt;td style="text-align: right;"&gt;2540&lt;/td&gt;
&lt;td style="text-align: right;"&gt;75,403,075&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;(The LaserWriter had a non-printable border of 0.42&amp;quot; per the user manual.)&lt;/p&gt;



&lt;figure&gt;
  
  &lt;img alt="Memory cost ~$7B/tb in 1983 but dropped to $900M/tb in 1985" class="fit-image" src="https://ztoz.blog/posts/postscript-code/historical-cost-of-computer-memory-and-storage.svg" /&gt;
  
  &lt;figcaption&gt;Figure C: Historical Cost of Computer Memory and Storage&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;h2 id="interesting-algorithms-and-designs"&gt;Interesting Algorithms and Designs&lt;/h2&gt;
&lt;p&gt;Reading through the source code, we found two items that are both interesting and representative of the whole: the design for handling input devices and the mini-reducer algorithm for strokes.&lt;/p&gt;
&lt;h3 id="input-device"&gt;Input Device&lt;/h3&gt;
&lt;p&gt;Although it is unclear if this code represents &amp;ldquo;Andy&amp;rsquo;s stupid input device,&amp;rdquo; the &lt;code&gt;InputDevice&lt;/code&gt; design demonstrates many of the common patterns seen within the code. Input devices are used to insert graphics into the document.&lt;/p&gt;
&lt;p&gt;The definition of an input device (from &lt;code&gt;graphics.h&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c"&gt;  &lt;span style="color: #66d9ef;"&gt;typedef&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;struct&lt;/span&gt;
    {
    procedure (&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;SetUpImage)( &lt;span style="color: #75715e;"&gt;/* StreamHandle sh; */&lt;/span&gt; );
      &lt;span style="color: #75715e;"&gt;/* function that sets up the input device for subsequent calls to all
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         other input device procedures.  The StreamHandle is assumed to
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         refer to an open stream on an image file (of the type understood by
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         this input device) positioned at the beginning. */&lt;/span&gt;
    procedure (&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;InputMatrix)( &lt;span style="color: #75715e;"&gt;/* Matrix matrix;*/&lt;/span&gt; );
      &lt;span style="color: #75715e;"&gt;/* function that fills in the supplied matrix with values that map
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         from input DeviceCoord's to positions in the unit square in user
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         space.  */&lt;/span&gt;
    ImageSlice (&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;NextSlice)();
      &lt;span style="color: #75715e;"&gt;/* function that returns an ImageSlice that refers to a portion of the
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         current image.  A returned ImageSlice-&amp;gt;width of zero indicates that
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         no more image remains.  The image algorithm requires that a one-pixel
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         buffer zone surround the rectangle actually used for imaging.  Thus,
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         the minimum useful ImageSlice returned must be at least 3 pixels
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         high and wide.  At internal boundaries in the image, the input device
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         must buffer two lines at the boundary of a previous slice and
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         include those lines in a subsequent slice.  I.e., the input device
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         must supply slices consisting of a rectangular tile and a one-pixel
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         wide frame around that tile such that the tiles completely cover the
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         input image.  The ImageSliceRec and pixel storage returned by this
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;         function may be reused for subsequent calls.  */&lt;/span&gt;
    }
InputDeviceRec, &lt;span style="color: #f92672;"&gt;*&lt;/span&gt;InputDevice;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An input device is an interface, implemented via function pointers within a struct, that contains three operations: a &lt;code&gt;SetUpImage&lt;/code&gt; function that &amp;ldquo;constructs&amp;rdquo; the internal data structures via a handle to a stream, a &lt;code&gt;InputMatrix&lt;/code&gt; function for accessing the transformation matrix (a &lt;code&gt;Matrix&lt;/code&gt; is for transformation, not general matrix storage), and a &lt;code&gt;NextSlice&lt;/code&gt; function that acts as an iterator for the output pixels. Since images can easily exceed the memory capacity of devices of the time, and potentially exceed the disk storage capacity, iterators are commonly used to &amp;ldquo;chunk&amp;rdquo; large amounts of data into manageable increments.&lt;/p&gt;
&lt;p&gt;We can see how the input devices are used through the &lt;code&gt;image&lt;/code&gt; PostScript command. &lt;code&gt;image&lt;/code&gt; takes five arguments: width and heigh of the image, the bits per pixel, the transformation matrix, and a PostScript procedure for the image data. For example, this &lt;a href="https://paulbourke.net/dataformats/postscript/"&gt;example from Paul Bourke&lt;/a&gt; renders a small document icon:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-postscript"&gt;&lt;span style="color: #ae81ff;"&gt;100&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;200&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;translate&lt;/span&gt;
&lt;span style="color: #ae81ff;"&gt;26&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;34&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;scale&lt;/span&gt;
&lt;span style="color: #ae81ff;"&gt;26&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;34&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;8&lt;/span&gt; [&lt;span style="color: #ae81ff;"&gt;26&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;-34&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;34&lt;/span&gt;]
{&amp;lt;
&lt;span style="color: #a6e22e;"&gt;ffffffffffffffffffffffffffffffffffffffffffffffffffff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff000000000000000000000000000000000000ffffffffffffff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefef0000ffffffffffff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefef00ce00ffffffffff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefef00cece00ffffffff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefef00cecece00ffffff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefef00cececece00ffff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefef00000000000000ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efef000000ef000000ef000000ef0000ef0000efefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efef000000ef00000000ef00000000ef000000efefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efef0000ef00000000000000ef000000ef0000efefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff00efefefefefefefefefefefefefefefefefefefefefef00ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ff000000000000000000000000000000000000000000000000ff&lt;/span&gt;
&lt;span style="color: #a6e22e;"&gt;ffffffffffffffffffffffffffffffffffffffffffffffffffff&lt;/span&gt;
&amp;gt;}
&lt;span style="color: #a6e22e;"&gt;image&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The PostScript &lt;code&gt;image&lt;/code&gt; command is implemented by the &lt;code&gt;psImage&lt;/code&gt; procedure in &lt;code&gt;image.c&lt;/code&gt; (the developers use the &lt;code&gt;ps&lt;/code&gt; prefix to identify functions that are called as PostScript commands). When invoked, &lt;code&gt;psImage&lt;/code&gt; processes its arguments and initializes the input device with the image data and the transformation matrix. Then, for each slice (rectangular section of the input image), the code defines a new trapezoidal path with transformed coordinates, and then calls &lt;code&gt;Reduce&lt;/code&gt;. Although &lt;code&gt;Reduce&lt;/code&gt; is not included in the source code distribution, we know it would rasterize the image onto the output by pulling from the input via the inverse transformation. (We have added //-style comments for clarity.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c"&gt;private procedure &lt;span style="color: #a6e22e;"&gt;psImage&lt;/span&gt;()
{
&lt;span style="color: #75715e;"&gt;// ... setup ...
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;&lt;/span&gt;(&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;gs&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;inputDevice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;SetUpImage)(sh);
(&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;gs&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;inputDevice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;InputMatrix)(&lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;imageMatrix);
&lt;span style="color: #75715e;"&gt;// ... math to transform inputs to outputs and for the inverse ...
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;&lt;/span&gt;&lt;span style="color: #66d9ef;"&gt;do&lt;/span&gt;
  {
  curImageSlice &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; (&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;gs&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;inputDevice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;NextSlice)();
  &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (curImageSlice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;width &lt;span style="color: #f92672;"&gt;==&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;) &lt;span style="color: #66d9ef;"&gt;break&lt;/span&gt;;
  NewPathIsClip(true);
  FeedPathToReducer(&lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;gs&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;clip, CallNewPoint, ReducerClosePath);
  c.x &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; curImageSlice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;origin.x &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;;  c.y &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; curImageSlice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;origin.y &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;;
  NewPathIsClip(false);
  CallNewPoint(TransformCoord(c, &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;imageTransform));
  c.x &lt;span style="color: #f92672;"&gt;+=&lt;/span&gt; curImageSlice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;width &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;;
  CallNewPoint(TransformCoord(c, &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;imageTransform));
  c.y &lt;span style="color: #f92672;"&gt;+=&lt;/span&gt; curImageSlice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;height &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;;
  CallNewPoint(TransformCoord(c, &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;imageTransform));
  c.x &lt;span style="color: #f92672;"&gt;-=&lt;/span&gt; curImageSlice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;width &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;;
  CallNewPoint(TransformCoord(c, &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;imageTransform));
  ReducerClosePath();
  Reduce(CallDeviceImageTrap, true, false);
  } &lt;span style="color: #66d9ef;"&gt;while&lt;/span&gt; (true);
} &lt;span style="color: #75715e;"&gt;/* end of psImage */&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The code is surprisingly generalizable &amp;mdash; once an image is rasterized, it can then be inserted into the document. Later versions of PostScript included support for compressed images, but that was more for convenience than necessity. As long as chunks of the image could fit within memory, they could be translated into the final output, even with additional transformations.&lt;/p&gt;
&lt;h3 id="mini-reducer-for-simple-quadrilaterals"&gt;Mini-Reducer for Simple Quadrilaterals&lt;/h3&gt;
&lt;p&gt;The &amp;ldquo;heart of the graphics engine,&amp;rdquo; the reducer algorithm translates arbitrary shapes into a raster image. Although the reducer algorithm is still considered a trade secret by Adobe forty years later, and thus not part of the source code release, (Warnock and Geschke 2019) provided a high-level description of the algorithm. The algorithm uses a plane-sweep approach to convert arbitrary shapes into non-overlapping trapezoids. The trapezoids are then rasterized to the final output. The original implementation used floating point, but this led to errors where line segments would &amp;ldquo;braid&amp;rdquo; around each other. Apple loaned one of their programmers, Jerome Coonen, who helped convert the algorithm to use fixed point.&lt;/p&gt;
&lt;p&gt;While the full reducer is absent, the source code does include a &amp;ldquo;mini-reducer&amp;rdquo;, as it is commented, within &lt;code&gt;stroke.c&lt;/code&gt;. One of the better commented sections of code, the algorithm reduces simple cases of convex quadrilaterals (i.e. a four-sided polygon with all interior angles less than 180 degrees) that lie completely within the clipping region to a rasterized image. In the code below, the /* comments are original while the // comments are added by the author.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div&gt;
&lt;table style="border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block;"&gt;&lt;tr&gt;&lt;td style="vertical-align: top; padding: 0; margin: 0; border: 0;"&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 1
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 2
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 3
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 4
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 5
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 6
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 7
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 8
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt; 9
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;10
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;11
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;12
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;13
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;14
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;15
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;16
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;17
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;18
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;19
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;20
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;21
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;22
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;23
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;24
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;25
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;26
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;27
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;28
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;29
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;30
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;31
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;32
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;33
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;34
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;35
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;36
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;37
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;38
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;39
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;40
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;41
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;42
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;43
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;44
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;45
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;46
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;47
&lt;/span&gt;&lt;span style="margin-right: 0.4em; padding: 0 0.4em 0 0.4em; color: #7f7f7f;"&gt;48
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-c"&gt;private procedure &lt;span style="color: #a6e22e;"&gt;FastFillQuad&lt;/span&gt;(c1, c2, c3, c4)  Coord c1, c2, c3, c4;
{ &lt;span style="color: #75715e;"&gt;/* this mini-reducer is for use only on convex quadrilaterals completely
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;     contained in the clipping region */&lt;/span&gt;
QuadCorner pq[&lt;span style="color: #ae81ff;"&gt;4&lt;/span&gt;]; &lt;span style="color: #75715e;"&gt;/* to hold pointers to qc[0-3] in bottom-up order */&lt;/span&gt;
QuadCorner qcptr, t;
&lt;span style="color: #75715e;"&gt;// y top, y bottom, x top left, x top right, x bottom left, x bottom right, temporary
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;&lt;/span&gt;Fixed yt, yb, xtl, xtr, xbl, xbr, tfixed;

&lt;span style="color: #75715e;"&gt;// Fix(a.b) returns the fractional portion, b, or the lower 16 bits
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;&lt;/span&gt;qc[&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;].c.x &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; Fix(c1.x); qc[&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;].c.y &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; Fix(c1.y);
qc[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;].c.x &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; Fix(c2.x); qc[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;].c.y &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; Fix(c2.y);
qc[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;].c.x &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; Fix(c3.x); qc[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;].c.y &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; Fix(c3.y);
qc[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;].c.x &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; Fix(c4.x); qc[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;].c.y &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; Fix(c4.y);

&lt;span style="color: #75715e;"&gt;/* bubble sort pointers to qc[0 - 3] in pq[0 - 3] so that
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;   lowest (y,x) comes first. */&lt;/span&gt;
&lt;span style="color: #75715e;"&gt;// sorts only on y; x coordinates are handled in the sweep
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;&lt;/span&gt;pq[&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;] &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;qc[&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;];  pq[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;] &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;qc[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;];  pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;] &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;qc[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;];  pq[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;] &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;qc[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;];
Bubble(pq[&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;],pq[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;])
Bubble(pq[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;],pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;])
Bubble(pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;],pq[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;])
Bubble(pq[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;],pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;])
Bubble(pq[&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;],pq[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;])
Bubble(pq[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;],pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;])

yb &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.y;  xbl &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; xbr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x;  yt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.y;
&lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (yt &lt;span style="color: #f92672;"&gt;==&lt;/span&gt; yb)  &lt;span style="color: #75715e;"&gt;/* horizontal bottom -- no initial trapezoid */&lt;/span&gt;
  {xbr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x; &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (xbr &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt; xbl) {tfixed &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; xbr; xbr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; xbl; xbl &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; tfixed;}}
&lt;span style="color: #66d9ef;"&gt;else&lt;/span&gt;  QuadTrap(pq[&lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;],pq[&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;])
yt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.y;
&lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (yt &lt;span style="color: #f92672;"&gt;!=&lt;/span&gt; yb)  QuadTrap(pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;],pq[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;])
yt &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.y;
&lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (yt &lt;span style="color: #f92672;"&gt;==&lt;/span&gt; yb)
  {
  &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (yt &lt;span style="color: #f92672;"&gt;==&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.y)
    {
    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt;      (xbl &lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x) xbl &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x;
    &lt;span style="color: #66d9ef;"&gt;else&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;if&lt;/span&gt; (xbr &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x) xbr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;2&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x;
    &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt;      (xbl &lt;span style="color: #f92672;"&gt;&amp;gt;&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x) xbl &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x;
    &lt;span style="color: #66d9ef;"&gt;else&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;if&lt;/span&gt; (xbr &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x) xbr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x;
    }
  &lt;span style="color: #66d9ef;"&gt;else&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt;;
  }
xtr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; xtl &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; pq[&lt;span style="color: #ae81ff;"&gt;3&lt;/span&gt;]&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x;
(&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;gs&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;outputDevice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;ColorTrap)
  (yt, yb, xtl, xtr, xbl, xbr, gs&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;color, gs&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;screen);
}  &lt;span style="color: #75715e;"&gt;/* end of FastFillQuad */&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;QuadTrap&lt;/code&gt; is a macro defined as:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-c"&gt;&lt;span style="color: #75715e;"&gt;#define QuadTrap(qcp1,qcp2) { \
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;  xtr = qcp1-&amp;gt;c.x; \
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;  &lt;/span&gt;&lt;span style="color: #75715e;"&gt;// ptr1 is "next" while ptr2 is "previous" in the doubly-linked list of the path
&lt;/span&gt;&lt;span style="color: #75715e;"&gt;&lt;/span&gt;  qcptr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; (qcp2&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;ptr1 &lt;span style="color: #f92672;"&gt;==&lt;/span&gt; qcp1) &lt;span style="color: #f92672;"&gt;?&lt;/span&gt; qcp2&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;ptr2 : qcp2&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;ptr1; \
  xtl &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; qcp2&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x \
   &lt;span style="color: #f92672;"&gt;+&lt;/span&gt; Fix(FReal(yt &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; qcp2&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.y) &lt;span style="color: #f92672;"&gt;*&lt;/span&gt; FReal(qcptr&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; qcp2&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.x) \
         &lt;span style="color: #f92672;"&gt;/&lt;/span&gt; FReal(qcptr&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.y &lt;span style="color: #f92672;"&gt;-&lt;/span&gt; qcp2&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;c.y)); \
  &lt;span style="color: #66d9ef;"&gt;if&lt;/span&gt; (xtr &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt; xtl) {tfixed &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; xtr; xtr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; xtl; xtl &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; tfixed;} \
  (&lt;span style="color: #f92672;"&gt;*&lt;/span&gt;gs&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;outputDevice&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;ColorTrap) \
    (yt, yb, xtl, xtr, xbl, xbr, gs&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;color, gs&lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt;screen); \
  yb &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; yt;  xbl &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; xtl;  xbr &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; xtr; \
  }
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Figure D illustrates how an example quadrilateral is filled with trapezoids. The left-most image traces the algorithm through line 26, the second through line 30, the third through line 43, and the fourth through the final statement of the function on lines 45 and 46. In this algorithm, a trapezoid may be a triangle (one side has length zero) and, in this example, two triangles are produced. Starting from the bottom of the polygon, the algorithm splits the polygon via horizontal scan beams based on the polygon&amp;rsquo;s vertices. The next trapezoid&amp;rsquo;s bottom edge is based on the former trapezoid&amp;rsquo;s top edge. Since the algorithm is only given limited, albeit common, inputs (convex quadrilateral completely within the clipping region), the algorithm only needs to handle various rotations of the quadrilateral.&lt;/p&gt;



&lt;figure&gt;
  
  &lt;img alt="Three steps to fill quadrilateral with trapezoids, bottom-up" class="fit-image" src="https://ztoz.blog/posts/postscript-code/FastFillQuad-example2.png" /&gt;
  
  &lt;figcaption&gt;Figure D: Algorithm fills quadrilateral with trapezoids&lt;/figcaption&gt;
&lt;/figure&gt;


&lt;p&gt;A similar algorithm for decomposing convex polygons into trapezoids is briefly described in &lt;a href="https://dl.acm.org/doi/10.1145/800224.806789"&gt;Parallel Processing and image synthesis and anti-aliasing&lt;/a&gt;, while a more general version is Vatti&amp;rsquo;s polygon clipping algorithm (&lt;a href="https://dl.acm.org/doi/10.1145/129902.129906"&gt;A generic solution to polygon clipping&lt;/a&gt;). We consider it possible that Adobe&amp;rsquo;s reducer is very similar to Vatti&amp;rsquo;s algorithm as both use a plane sweep approach.&lt;/p&gt;
&lt;p&gt;Implementation-wise, it is notable that while the algorithm stores positions in fixed point representation, the actual operations are performed using floating point operations. The &lt;code&gt;FReal&lt;/code&gt; macro casts the argument to a float and then divides by &lt;code&gt;1&amp;lt;&amp;lt;16&lt;/code&gt;, while &lt;code&gt;Fix&lt;/code&gt; does the opposite by casting the argument to an integer and multiplying by &lt;code&gt;1&amp;lt;&amp;lt;16&lt;/code&gt;. In both cases, the compiler could highly optimize these operations as they are modifications by powers of two.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;FastFillQuad&lt;/code&gt; avoids making any function calls except for the ColorTrap calls. Macros are used to encapsulate reused code but make free use of referencing and changing variables that are not passed as arguments. While unhygienic, code profiling may have indicated the loss of clarity as worth the increase in performance.&lt;/p&gt;
&lt;h2 id="alternative-paths--competitors"&gt;Alternative Paths / Competitors&lt;/h2&gt;
&lt;p&gt;While there were a variety of proprietary printer languages that supported various raster effects, the only digital competitor to PostScript was Xerox&amp;rsquo;s Interpress. The non-commercial TeX project solved similar issues of rasterization using Device Independent files, but their solution had limited capability.&lt;/p&gt;
&lt;h3 id="interpress"&gt;Interpress&lt;/h3&gt;
&lt;p&gt;An observer in the mid-1980s would have likely considered PostScript the underdog to Xerox&amp;rsquo;s Interpress. Interpress had several advantages: it had been under development for at least twice as long as PostScript, was funded by Xerox which had pioneered many desktop publishing technologies and had lots of capital, and had a captive PARC community to validate the design. However, Xerox management dithered away their advantages (as they did for the rest of their PARC investment) and Adobe won.&lt;/p&gt;
&lt;p&gt;Ignoring the business side, based on their technical merits, how did Interpress and PostScript compare? In a &lt;a href="https://www.tech-insider.org/unix/research/1985/0301.html"&gt;pair of Usenet postings to the fa.laser-lovers newsgroup&lt;/a&gt; in 1985, Brian Reid and Jerry Mendelson compared the two systems. Brian Reid based his analysis on the publicly released 1984 Interpress manual (which he noted to his surprise was created via Press, not Interpress). Reid had worked for Xerox as a consultant on the Interpress project alongside Geschke, Warnock, Lampson, and Sproull. Sproull was formerly Reid&amp;rsquo;s thesis advisor and Reid later wrote the &amp;ldquo;Green Book,&amp;rdquo; an introduction to the PostScript language. When Mendelson wrote his reply, he was a retired Xerox research fellow, but an active Xerox consultant. He had also worked on the transition of Interpress from a research project into a commercial product. Thus, both writers had first-hand experience with the technology and Xerox&amp;rsquo;s culture.&lt;/p&gt;
&lt;p&gt;Both writers agreed that Interpress had better support for interacting and controlling printers, such as controlling page order, informing accounting systems of printing expenses, and selecting optional features such as stapling. Interpress files also imposed a structure on the program that meant the expected page count could be discerned from the structure and individual pages could be printed in any order. (This feature would be part of the PDF format.) While the lack of this structure could be used to an advantage for PostScript, for instance if a PostScript program was translating another format such as QuickDraw to itself, commercial printers and companies with shared printers appreciated the deterministism of Interpress.&lt;/p&gt;
&lt;p&gt;Mendelson considered Interpress' binary encoding format superior to PostScript&amp;rsquo;s character stream due to the binary format&amp;rsquo;s reduced space requirements. Mendelson also considered the binary format a better fit for the Xerox Network Systems environment and, indeed, Interpress supported fetching resources remotely via XNS paths, while PostScript required users to place any external files into the printer&amp;rsquo;s local filesystem (if it existed). Reid was more ambivalent, treating the two formats as expressing different trade-offs.&lt;/p&gt;
&lt;p&gt;Language-wise, both PostScript and Interpress were stack-based languages with very similar functions and design. Both writers considered PostScript more flexible and &amp;ldquo;open&amp;rdquo; as users could write operators that replaced system operators and PostScript could generate and execute its own code.&lt;/p&gt;
&lt;p&gt;Graphically, although both systems had similar origins, PostScript supported curves and arbitrary rotations of objects in 1985, while Interpress did not. Mendelson expected Interpress to close the gap over time, while Reid wondered why the gap existed at all since the underlying algorithms were publicly documented and Interpress had had more development time.&lt;/p&gt;
&lt;p&gt;Interpress did not document its approach to fonts in 1985, so Reid was unable to compare the two systems, but Mendelson considered their approach to fonts equivalent. However, if we look at how Interpress handled fonts as described in the 1988 &lt;em&gt;Interpress: the Source Book&lt;/em&gt;, fonts were restricted to 90 degree rotations in the Commercial and Publication feature sets. Only the most expensive (and limited in availability) Professional feature set supported arbitrary rotations. Further, the book warns that fonts may drift over time and, if a requested font is not available, the Interpress system will choose an approximate font. This means that character spacing and alignment may be broken if a different font than the original is chosen. Interpress contained a &lt;code&gt;correct&lt;/code&gt; operator that attempted to fix these issues, but PostScript, with the ability to embed fonts, never required a similar function.&lt;/p&gt;
&lt;p&gt;The strongest technical advantage for Interpress was the document structure, which PDF incorporated in the early 90s. However, PostScript&amp;rsquo;s adaptability was highly useful for integrating systems at the time, such as Apple&amp;rsquo;s QuickDraw. In terms of the other technical aspects, the fact that PostScript was fully-featured and available was more important than features that were &lt;em&gt;expected&lt;/em&gt; to be as good or better.&lt;/p&gt;
&lt;h3 id="device-independent-dvi-files"&gt;Device Independent (DVI) Files&lt;/h3&gt;
&lt;p&gt;Designed in 1979 as part of the TeX project, Device Independent (DVI) files act as an intermediary page description language. DVI files are read by a driver, which could render the DVI to the screen, a printer, or another representation. DVI shares similar advantages as the later developed PDF standard in that, while the format programatically builds a page, the language is not Turing-complete and thus can be interpreted with  predictable performance, capacity requirements, and assurance that processing will terminate. Additionally, the page count is fixed and each page can be rendered in any order.&lt;/p&gt;
&lt;p&gt;However, unlike PostScript and PDFs, DVI does not have a means to embed fonts. Fonts can only be referenced from DVIs, meaning they must be installed before being rendered. Further, DVI only supports rectangles as graphics. Graphics are normally embedded within the file as a &amp;lsquo;special,&amp;rsquo; and the driver is responsible for rendering them. DVI was never a real competitor to PostScript, but rather DVI was a bridge from TeX to PostScript, as authors would embed PostScript as the graphics format and convert everything to PostScript (and later PDF) for the printer.&lt;/p&gt;
&lt;h2 id="print-anything-philosophy"&gt;&amp;ldquo;Print Anything&amp;rdquo; Philosophy&lt;/h2&gt;
&lt;p&gt;The founder&amp;rsquo;s disagreements with Xerox may be boiled down to a philosophical difference between &amp;ldquo;print anything; throughput be damned&amp;rdquo; and &amp;ldquo;print most things; preserve our business model&amp;rdquo;. If a Press document was too complex for the printer, the job would be rejected. Xerox&amp;rsquo;s primary business billed by pages printed and thus throughput was key. This philosophy was carried forward in Interpress by limiting functionality by market segments and restricting the flexibility of the programs to improve page rendering times. In contrast (Perry 1988):&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Throughout the design of PostScript, speed was regularly traded off to ensure that any image would print. The group reasoned that if they built in all this functionality, they could eventually improve the performance; but if they left out functions, they might never be able to add them back in.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Within the source code, we find the building blocks of documents such as paths and sampled arrays are treated in the most general of fashion. This generality imposes more work on the program (and on development and testing effort), but the end user benefits by having fewer arbitrary limits placed on their design and creativity.&lt;/p&gt;
&lt;p&gt;For example, PostScript decomposed fonts into paths. PostScript allowed paths to be translated, rotated, skewed, and scaled arbitrarily. In contrast, contemporary systems treated fonts as a special case and &lt;em&gt;might&lt;/em&gt; allow fonts to be rotated 90 degrees, but heavily restricted scaling and skewing. As part of their market segmentation, Interpress only allowed font rotation by 90 in their lowest priced offering. (It is unclear if the Interpress code supported arbitrary rotation or not; a full-featured version was never released.) Decomposing fonts into paths required inventing new algorithms and, to maintain quality, entirely new ways of rendering fonts. Creatives immediately embraced the flexibility. This flexibility was a key competitive advantage for Adobe until the Font War of the 1990s led to competitors implementing similar algorithms.&lt;/p&gt;
&lt;h3 id="limits-of-print-anything"&gt;Limits of &amp;ldquo;Print Anything&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;As a Turing-complete language, PostScript programs could exceed the limits of the device, either by exceeding the stack depth, requiring more than the allocated number of dictionary keys, or otherwise exhausting memory or patience. The &amp;ldquo;Red Book&amp;rdquo; documented many of these static limits. The same language power, however, allowed programmers the possibility of rewriting the program to fit within the device&amp;rsquo;s capabilities.&lt;/p&gt;
&lt;h2 id="deployment-and-history-after-version-10"&gt;Deployment and History after Version 1.0&lt;/h2&gt;
&lt;p&gt;After 1.0, PostScript&amp;rsquo;s font handling algorithms were overhauled and dramatically improved in quality. PostScript was embedded into Apple&amp;rsquo;s new LaserWriter in 1985 and, three years later, had been adopted by 23 manufacturers (Perry 1988). Typefaces proved to be a fertile marketplace as foundries converted their back catalogs into digital forms and designers reveled in the freedom to easily experiment with new fonts. Adobe released two major updates to PostScript in 1991 and 1997, improving reliability and expanding support for color, page support, and ways to embed images and other media.&lt;/p&gt;
&lt;p&gt;Adobe&amp;rsquo;s introduction of PDF in 1993 began to obsolete PostScript. PDF used the same graphics model as PostScript, but dropped the Turing-complete language. PDF could thus be printed at the same or higher throughput as PostScript, but also gained the advantage that individual pages could be referenced (an advantage Interpress had) and rendered separately. As PDFs gained support for forms and interactivity, clients adopted PDFs as a key workflow technology and their use exploded in popularity.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;p&gt;(Adobe 1993) Adobe Systems Incorporated. 1993. “Adobe Type 1 Font Format.” Addison-Wesley Publishing Company, Inc. &lt;a href="https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf"&gt;https://adobe-type-tools.github.io/font-tech-notes/pdfs/T1_SPEC.pdf&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Perry 1988) Perry, Tekla. 1988. “Inventing Postscript, the Tech That Took the Pain out of Printing.” IEEE Spectrum, May 1, 1988. &lt;a href="https://spectrum.ieee.org/adobe-postscript"&gt;https://spectrum.ieee.org/adobe-postscript&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Reid 1988) Reid, Glenn. 1988. PostScript Language Program Design. Addison-Wesley Publishing Company, Inc.&lt;/p&gt;
&lt;p&gt;(Sproull and Reid 1983) Sproull, Robert F., and Brian Reid. 1983. &lt;em&gt;Introduction to Interpress&lt;/em&gt;. XSIG 038306. El Segundo, CA: Xerox Corporation. &lt;a href="http://www.bitsavers.org/pdf/xerox/interpress/XSIG_038306_Introduction_to_Interpress_Jun1983.pdf"&gt;http://www.bitsavers.org/pdf/xerox/interpress/XSIG_038306_Introduction_to_Interpress_Jun1983.pdf&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Warnock and Wyatt 1982) Warnock, John E, and Douglas K Wyatt. 1982. “A Device Independent Graphics Imaging Model for Use with Raster Devices.” Proceedings of the 9th Annual Conference on Computer Graphics and Interactive Techniques 16 (3): 313–19. &lt;a href="https://doi.org/10.1145/965145.801297"&gt;https://doi.org/10.1145/965145.801297&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Warnock 2012) Warnock, John E. 2012. “Simple Ideas That Changed Printing and Publishing.” Proceedings of the American Philosophical Society 156 (4): 363–78. &lt;a href="http://www.jstor.org/stable/23558230"&gt;http://www.jstor.org/stable/23558230&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;(Warnock and Geschke 2019) Warnock, John E, and Charles Geschke. 2019. “Founding and Growing Adobe Systems, Inc.” IEEE Annals of the History of Computing 41 (3): 24–34. &lt;a href="https://doi.org/10.1109/MAHC.2019.2923397"&gt;https://doi.org/10.1109/MAHC.2019.2923397&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;(Xerox 2022) “The Xerox 2700 Story.” 2022. Association of Retired Xerox Employees. &lt;a href="https://archive.org/details/the-xerox-2700-story"&gt;https://archive.org/details/the-xerox-2700-story&lt;/a&gt;.&lt;/p&gt;</description><author>ℤ→ℤ</author><pubDate>Thu, 26 Sep 2024 18:55:38 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/postscript-code/</guid></item><item><title>Post-OCSP certificate revocation in the Web PKI</title><link>https://seirdy.one/posts/2024/09/25/post-ocsp-revocation/</link><description>OCSP, including OCSP Stapling, is leaving the Web PKI. Here's a complete look at revocation beyond OCSP: its past, present, and possible futures.</description><author>All content on Seirdy’s Home</author><pubDate>Thu, 26 Sep 2024 17:25:27 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/posts/2024/09/25/post-ocsp-revocation/</guid></item><item><title>Hawkpost enters “maintenance only” mode</title><link>https://blog.ovalerio.net/archives/2990</link><description>In practice this already happened a couple of years ago, now we are just making it official. For those who don&amp;#8217;t know, Hawkpost is a side project that I started while at Whitesmith back in 2016 (8+ years ago). I&amp;#8217;ve written about it here in the blog on several occasions. To sum it up, it [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Thu, 26 Sep 2024 16:47:25 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/2990</guid></item><item><title>Using Omnivore to save pages</title><link>https://ilearnt.com/blog/omnivore/</link><description>&lt;p&gt;Sometimes it is handy to save a webpage for later and there are lots of ways to do it.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Thu, 26 Sep 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/omnivore/</guid></item><item><title>Practical Deep Learning, Lesson 2, Rowing Classifier</title><link>https://www.danielcorin.com/til/fastai/lesson2-rowing-classifier/</link><description>Practical Deep Learning, Lesson 2, Rowing Classifier</description><author>Thought Eddies</author><pubDate>Thu, 26 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/fastai/lesson2-rowing-classifier/</guid></item><item><title>Why You Should Resist Surveillance</title><link>https://lambdaland.org/posts/2024-09-26_resist_surveillance/</link><description>&lt;p&gt;I got to visit the Stasi museum in Berlin this week, and it gave me a newfound appreciation for why it&amp;rsquo;s important to resist surveillance. Interestingly, surveillance is not exclusively limited to one kind of government: it can appeal to both left- and right-wing governments, and corporations in the digital age use surveillance to make money. In every form, surveillance is evil and must be resisted.&lt;/p&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;Stasi = Abbreviation of &lt;em&gt;Stadt Sicherheit&lt;/em&gt; (&amp;ldquo;state security&amp;rdquo; in German).&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;For those of you who don&amp;rsquo;t know, the Stasi were the secret police of the communist regime in East Germany. They employed massive resources to spy upon and surveil East German citizens, and they kept careful track of anyone who showed signs of dissatisfaction with the state. They employed massive numbers of informal collaborators—mere citizens—who would spy on their neighbors and report back to the Stasi.&lt;/p&gt;
&lt;p&gt;I got to go there with my dad, and he remarked on how much of a drag on the economy the Stasi must have been. Think of it: the country funneled incredibly vast resources into spying on its own citizens and for what? It was claimed to be a protection against domestic terrorists and the like, but in reality, the Stasi was oppressing and discouraging anyone who might be disgruntled with those in power and the way they were running things.&lt;/p&gt;
&lt;div class="epigraph"&gt;
&lt;blockquote&gt;
&lt;p&gt;The Stasi were nominally there to ensure the security of the &lt;em&gt;state&lt;/em&gt;. In reality they were ensuring the security of the &lt;em&gt;regime&lt;/em&gt;.&lt;/p&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p&gt;This is such an important distinction! A state has a legitimate interest in protecting its own citizens from terrorism and preventing criminal activity. But a democratic government &lt;em&gt;must never&lt;/em&gt; instill fear in its citizens because of their opinions with how things should change. For a democracy to work the &lt;em&gt;only&lt;/em&gt; legitimate way to obtain the consent of the governed is if they can think express their ideas, frustrations, and concerns freely. (In a proper non-violent manner, of course.)&lt;/p&gt;
&lt;div class="epigraph"&gt;
&lt;blockquote&gt;
&lt;p&gt;Surveillance suppresses free thought.&lt;/p&gt;&lt;/blockquote&gt;
&lt;/div&gt;
&lt;p&gt;Think about it: if you know that you will be quizzed on something, you will pay better attention to what is being taught. If you know that your actions are being watched by someone who has the ability to influence your life, you start to change your behavior. Remember, there are people who, if they don&amp;rsquo;t like you, can make your life miserable. Imagine what detailed, personal knowledge enables for people who don&amp;rsquo;t like the categories you fit into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The owner of an HOA who doesn&amp;rsquo;t like your political leanings goes to excessive lengths to fine you for minor infractions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An insurance agency illegally profiles you and considers you higher risk for their portfolio because of some underlying health conditions and life circumstances you are in. They then find ways to decline service or coverage when you need it most.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Someone is elected to office. He knows you didn&amp;rsquo;t vote for him, so he puts pressure through back channels to slow down or prevent you from getting the services you need. Now, for example, you can&amp;rsquo;t travel internationally to visit your family because your file is marked for some reason and you&amp;rsquo;re fighting a Kafkaesque process to clear it up.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A company uses your internet browsing history to profile your interests, fears, and hopes. They use sophisticated artificial intelligence to craft social media feeds crafted to be highly addicting to you. They erode your self-discipline so that they can make some more money.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Surveillance is the means why which massive control is enabled. If you care about protecting &lt;em&gt;your&lt;/em&gt; freedom and &lt;em&gt;your&lt;/em&gt; agency, resist surveillance—whether it be by a government or a corporation. They may say that it&amp;rsquo;s in your own best interest: to make you safer or to serve up content that is catered to your tastes, but they are lying. It is all done in self-interest: to protect their regime or to turn their profits—all at &lt;em&gt;your&lt;/em&gt; expense.&lt;/p&gt;
&lt;p&gt;Take your privacy back. Visiting the &lt;a href="https://eff.org/"&gt;EFF&lt;/a&gt; is a great way to get started.&lt;/p&gt;</description><author>Ashton Wiersdorf on Lambda Land</author><pubDate>Thu, 26 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2024-09-26_resist_surveillance/</guid></item><item><title>Germinal acceleration</title><link>https://asemic-horizon.com/2024/09/25/germinal-acceleration/</link><description>A barely-even-there distinction needs to be drawn between axiom and axiomatics. This distinction is best seen when the axiom hasn&amp;#8217;t degerminated yet, that is, when it plays the role of a desideratum. Then, desiderics makes itself known as the abstractmost concupiscence that desiderata try their best to infuse with definite terminality. But if an axiom [&amp;#8230;]</description><author>asemic horizon</author><pubDate>Wed, 25 Sep 2024 19:48:36 GMT</pubDate><guid isPermaLink="true">https://asemic-horizon.com/2024/09/25/germinal-acceleration/</guid></item><item><title>Kitchensink</title><link>https://julianwachholz.dev/kitchensink/</link><description>&lt;h1 id="big-header"&gt;Big header &lt;a class="anchor" href="#big-header"&gt;#&lt;/a&gt;&lt;/h1&gt;&lt;h2 id="medium-header"&gt;Medium header &lt;a class="anchor" href="#medium-header"&gt;#&lt;/a&gt;&lt;/h2&gt;&lt;h3 id="small-header"&gt;Small header &lt;a class="anchor" href="#small-header"&gt;#&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;bold&lt;/strong&gt;&lt;br /&gt;
&lt;em&gt;italics&lt;/em&gt;&lt;br /&gt;
&lt;del&gt;strikethrough&lt;/del&gt;&lt;br /&gt;
marked text&lt;br /&gt;
&lt;ins&gt;insert me&lt;/ins&gt;&lt;br /&gt;
Hello&lt;sup&gt;superscript&lt;/sup&gt;&lt;br /&gt;
Hello&lt;sub&gt;subscript&lt;/sub&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Generic list item&lt;/li&gt;
&lt;li&gt;Generic list item&lt;/li&gt;
&lt;li&gt;Generic list item&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Numbered list item&lt;/li&gt;
&lt;li&gt;Numbered list item&lt;/li&gt;
&lt;li&gt;Numbered list item&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I drink H&lt;sub&gt;2&lt;/sub&gt;O at the 6&lt;sup&gt;th&lt;/sup&gt; and 12&lt;sup&gt;th&lt;/sup&gt; hours of the day.&lt;/p&gt;
&lt;article class="admonition warning"&gt;
&lt;header&gt;&lt;strong&gt;Warning&lt;/strong&gt;&lt;/header&gt;
&lt;p&gt;You are looking at the &lt;strong&gt;dev&lt;/strong&gt; documentation. Check out our &lt;a href="/stable/"&gt;stable&lt;/a&gt; documentation instead.&lt;/p&gt;
&lt;/article&gt;
&lt;p&gt;Here is a simple footnote,&lt;sup class="footnote-ref" id="fnref-1"&gt;&lt;a href="#fn-1"&gt;1&lt;/a&gt;&lt;/sup&gt; and here is a longer one.&lt;sup class="footnote-ref" id="fnref-2"&gt;&lt;a href="#fn-2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Then at the bottom of your post...&lt;/p&gt;
&lt;p&gt;This is a &lt;a href="http://www.example.com"&gt;link&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Bear chilling" src="https://i.imgur.com/3jxqrKP.jpeg" /&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is a quote.&lt;br /&gt;
It can span multiple lines!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We can also do fancy figures:&lt;/p&gt;
&lt;figure&gt;
[IMAGE=gbHJdmf]
&lt;figcaption&gt;&lt;p&gt;A cute doggy in the woods.&lt;/p&gt;
&lt;/figcaption&gt;

&lt;/figure&gt;
&lt;p&gt;And fancy block quotes:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A wise man once said MSG is good for you.&lt;/p&gt;
&lt;footer&gt;&lt;cite&gt;Joshua Weissman&lt;/cite&gt;&lt;/footer&gt;&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
  &lt;th&gt;Column 1&lt;/th&gt;
  &lt;th&gt;Column 2&lt;/th&gt;
  &lt;th style="text-align: center;"&gt;Flag&lt;/th&gt;
  &lt;th style="text-align: right;"&gt;Total&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
  &lt;td&gt;John&lt;/td&gt;
  &lt;td&gt;Doe&lt;/td&gt;
  &lt;td style="text-align: center;"&gt;✅&lt;/td&gt;
  &lt;td style="text-align: right;"&gt;$ 32.99&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
  &lt;td&gt;Mary&lt;/td&gt;
  &lt;td&gt;Smith&lt;/td&gt;
  &lt;td style="text-align: center;"&gt;❌&lt;/td&gt;
  &lt;td style="text-align: right;"&gt;$ 9.12&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Here's some inline code &lt;code&gt;var example = &amp;quot;hello!&amp;quot;;&lt;/code&gt; for example.&lt;/p&gt;
&lt;p&gt;Or spanning multiple lines with line numbers and highlights.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="linenos"&gt; 1&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;hello!&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="linenos"&gt; 2&lt;/span&gt;&lt;span class="nx"&gt;alert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="linenos"&gt; 3&lt;/span&gt;
&lt;span class="hll"&gt;&lt;span class="linenos"&gt; 4&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;myfn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;span class="hll"&gt;&lt;span class="linenos"&gt; 5&lt;/span&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;123&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;span class="linenos"&gt; 6&lt;/span&gt;&lt;span class="w"&gt;	&lt;/span&gt;
&lt;span class="linenos"&gt; 7&lt;/span&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="linenos"&gt; 8&lt;/span&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;obj&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt; 9&lt;/span&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;val&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="linenos"&gt;10&lt;/span&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="linenos"&gt;11&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;hr /&gt;
&lt;p&gt;A page break?&lt;/p&gt;
&lt;ul&gt;
&lt;li class="task-list-item"&gt;&lt;input checked="checked" class="task-list-item-checkbox" disabled="disabled" type="checkbox" /&gt;A task list&lt;/li&gt;
&lt;li class="task-list-item"&gt;&lt;input class="task-list-item-checkbox" disabled="disabled" type="checkbox" /&gt;Can be done&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;small&gt;the fineprint&lt;/small&gt;&lt;/p&gt;
&lt;p&gt;&lt;kbd&gt;Ctrl+S&lt;/kbd&gt;&lt;/p&gt;
&lt;p&gt;&lt;ins&gt;Insert&lt;/ins&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;Delete&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;Highlight&lt;/p&gt;
&lt;article class="admonition note"&gt;
&lt;header&gt;&lt;strong&gt;FAQ&lt;/strong&gt;&lt;/header&gt;
&lt;details&gt;
Open Up&lt;p&gt;You can nest some cool elements  together.&lt;/p&gt;
&lt;/details&gt;
&lt;hr /&gt;
&lt;details&gt;
More Things&lt;p&gt;Await those who are brave.&lt;/p&gt;
&lt;/details&gt;
&lt;/article&gt;
&lt;dl&gt;
&lt;dt&gt;Or a definition list&lt;/dt&gt;
&lt;dd&gt;Is cool&lt;/dd&gt;
&lt;dd&gt;is versatile&lt;/dd&gt;
&lt;dt&gt;More terms&lt;/dt&gt;
&lt;dd&gt;with definition&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;The &lt;abbr title="Hyper Text Markup Language"&gt;HTML&lt;/abbr&gt; specification
is maintained by the &lt;abbr title="World Wide Web Consortium"&gt;W3C&lt;/abbr&gt;.&lt;/p&gt;
&lt;h4 id="spooky-spoiler-ahead"&gt;Spooky spoiler ahead &lt;a class="anchor" href="#spooky-spoiler-ahead"&gt;#&lt;/a&gt;&lt;/h4&gt;&lt;div class="spoiler" tabindex="0"&gt;
&lt;p&gt;here is the spoiler content&lt;/p&gt;
&lt;p&gt;it will be hidden&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;An inline spoiler is surrounded with &lt;span class="spoiler" tabindex="0"&gt;and&lt;/span&gt;:
this is the &lt;span class="spoiler" tabindex="0"&gt;hidden text&lt;/span&gt;&lt;/p&gt;
&lt;section class="footnotes"&gt;
&lt;ol&gt;
&lt;li id="fn-1"&gt;&lt;p&gt;This is the first footnote.&lt;a class="footnote" href="#fnref-1"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li id="fn-2"&gt;&lt;p&gt;Here is another one.&lt;a class="footnote" href="#fnref-2"&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;</description><author>Julian Wachholz</author><pubDate>Wed, 25 Sep 2024 18:22:20 GMT</pubDate><guid isPermaLink="true">https://julianwachholz.dev/kitchensink/</guid></item><item><title>Book Review: Thinking in Systems</title><link>https://abdulapopoola.com/2024/09/25/book-review-thinking-in-systems/</link><description>The book discusses the fundamentals of systems theory, emphasizing interconnectedness, stock, and flow dynamics. It highlights systemic problems, such as addiction and resource management, and explores how common misconceptions can lead to ineffective approaches. It underscores the importance of understanding system behavior and structural change for driving impactful outcomes and sustainable solutions.</description><author>CodeKraft</author><pubDate>Wed, 25 Sep 2024 18:00:00 GMT</pubDate><guid isPermaLink="true">https://abdulapopoola.com/2024/09/25/book-review-thinking-in-systems/</guid></item><item><title>Experience versus energy</title><link>https://ilearnt.com/blog/experienceversusenergy/</link><description>&lt;p&gt;There is a well known &lt;a href="https://openside.group/the-parable-of-the-old-man-with-the-hammer" target="_blank"&gt;story&lt;/a&gt; about an old man fixing an engine.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Wed, 25 Sep 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/experienceversusenergy/</guid></item><item><title>AI Agentic Workflow Andrew Ng</title><link>https://prashamhtrivedi.in/ai_agentic_workflow_andrew_ng/</link><description>Youtube Video, via JS Party podcast episode Building LLM agents in JS
Notes Non Agentic workflow: Do it start to finish. Mostly zero shot prompts.
Agentic workflow: Revise, iterative, reflect, use tools if you need to&amp;hellip;.
Four design patterns. 1. Reflect: Produce one thing and ask another chat thread with different system prompt to evaluate it. E.g. Create a code, than ask a rubberduck debugger to read it line by line, or run the test suite and provide the result to LLM generated the code to evaluate it.</description><author>Prasham H Trivedi</author><pubDate>Wed, 25 Sep 2024 10:13:22 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/ai_agentic_workflow_andrew_ng/</guid></item><item><title>My Citizen Science Projects</title><link>https://ivymike.dev/my-citizen-science-projects.html</link><description>&lt;p&gt;I love contributing to various "citizen science" projects--things where I can buy a sensor or a raspberry pi, set it up, and contribute data to a larger project.  Someday I will talk more about each of these projects that I contribute to, but for now I'm just collecting high-level links …&lt;/p&gt;</description><author>IvyMike.dev</author><pubDate>Wed, 25 Sep 2024 10:00:00 GMT</pubDate><guid isPermaLink="true">https://ivymike.dev/my-citizen-science-projects.html</guid></item><item><title>How to make Cloudflare Origin certificates work</title><link>https://tsak.dev/posts/make-cloudflare-origin-certificates-work/</link><description>&lt;p&gt;Using a Cloudflare Tunnel and connecting to a local service serving via self-signed certificates forced me to enable
&lt;strong&gt;No TLS verify&lt;/strong&gt; in that tunnel&amp;rsquo;s &lt;strong&gt;TLS&lt;/strong&gt; settings. &lt;em&gt;Not ideal!&lt;/em&gt; Thankfully Cloudflare thought about that and allows
you to &lt;a href="https://developers.cloudflare.com/ssl/origin-configuration/origin-ca#deploy-an-origin-ca-certificate"&gt;create an origin certificate&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For this to work properly, I had to install &lt;strong&gt;Cloudflare&amp;rsquo;s Origin Root CA certificate&lt;/strong&gt; on my server running Ubuntu 22.04.5 LTS.&lt;/p&gt;
&lt;p&gt;First I &lt;a href="https://developers.cloudflare.com/ssl/origin-configuration/origin-ca/#cloudflare-origin-ca-root-certificate"&gt;downloaded one of the two origin root CA certificates&lt;/a&gt;. I grabbed the
&lt;a href="https://developers.cloudflare.com/ssl/static/origin_ca_rsa_root.pem"&gt;RSA PEM&lt;/a&gt;.&lt;/p&gt;</description><author>Look mum, I have a blog on tsak.dev</author><pubDate>Wed, 25 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://tsak.dev/posts/make-cloudflare-origin-certificates-work/</guid></item><item><title>Kimba Syndrome</title><link>https://taylor.town/oh-colonialism</link><description>Sure, maybe all this is legal; maybe it's also super shitty.</description><author>taylor.town</author><pubDate>Wed, 25 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/oh-colonialism</guid></item><item><title>Running Docker in an Azure Windows Virtual Machine – Not so fast!</title><link>https://nestenius.se/azure/running-docker-in-an-azure-windows-virtual-machine-not-so-fast/</link><description>&lt;p&gt;This blog post describes getting Docker up and running inside an Azure Windows Virtual Machine. This might sound like a simple task, but trust me, there are some surprises on the way that took me a day or so to figure out. Why run docker in an Azure Virtual Machine? As a trainer, I need [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/azure/running-docker-in-an-azure-windows-virtual-machine-not-so-fast/"&gt;Running Docker in an Azure Windows Virtual Machine – Not so fast!&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Tue, 24 Sep 2024 22:25:32 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/azure/running-docker-in-an-azure-windows-virtual-machine-not-so-fast/</guid></item><item><title>2024-09-24-001</title><link>https://srijan.ch/notes/2024-09-24-001</link><description>#Emacs #TIL : I learned about save-interprogram-paste-before-kill - which saves the existing system clipboard text into the kill ring before replacing it. This ensures that Emacs kill operations do not irrevocably overwrite existing clipboard text. A common workflow for me is to copy some text from a different application and paste it inside Emacs. But, if I want to first delete a word or region …</description><author>Srijan Choudhary, all posts</author><pubDate>Tue, 24 Sep 2024 20:20:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2024-09-24-001</guid></item><item><title>Elos and Benchmarking LLMs</title><link>https://www.quantable.com/ai/elos-and-benchmarking-llms/</link><description>&lt;p&gt;A new LLM model seems to be released every week. AI experts and influencers immediately start talking about where new model excels, and why you should drop whatever you&amp;#8217;re using now and getting in on the new hotness. It&amp;#8217;s very hard to know when this analysis is based upon actual data, and when it&amp;#8217;s just [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://www.quantable.com/ai/elos-and-benchmarking-llms/"&gt;Elos and Benchmarking LLMs&lt;/a&gt; appeared first on &lt;a href="https://www.quantable.com"&gt;Quantable Analytics&lt;/a&gt;.&lt;/p&gt;</description><author>Quantable Analytics</author><pubDate>Tue, 24 Sep 2024 18:26:45 GMT</pubDate><guid isPermaLink="true">https://www.quantable.com/ai/elos-and-benchmarking-llms/</guid></item><item><title>TIL how to configure additional headers in Gitlab’s nginx</title><link>https://www.zufallsheld.de/2024/09/24/til-how-to-configure-additional-heades-in-gitlabs-nginx/</link><description>&lt;p&gt;Recently, I had to configure some security headers in GitLab. GitLab uses Nginx as its web server, and it allows for easy configuration changes for &lt;a href="https://docs.gitlab.com/omnibus/settings/nginx.html"&gt;some&lt;/a&gt; settings. For instance, enabling &lt;span class="caps"&gt;HTTP&lt;/span&gt; to &lt;span class="caps"&gt;HTTPS&lt;/span&gt; redirection can be done simply by&amp;nbsp;setting &lt;code&gt;nginx['redirect_http_to_https'] = true&lt;/code&gt; in&amp;nbsp;the &lt;code&gt;gitlab.rb&lt;/code&gt; configuration&amp;nbsp;file.&lt;/p&gt;
&lt;!-- PELICAN_END_SUMMARY --&gt;

&lt;p&gt;However …&lt;/p&gt;</description><author>zufallsheld</author><pubDate>Tue, 24 Sep 2024 16:30:00 GMT</pubDate><guid isPermaLink="true">https://www.zufallsheld.de/2024/09/24/til-how-to-configure-additional-heades-in-gitlabs-nginx/</guid></item><item><title>Winning is not everything</title><link>https://ilearnt.com/blog/winningisnoteverything/</link><description>&lt;p&gt;I support Somerset cricket club and have done since I was a kid.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Tue, 24 Sep 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/winningisnoteverything/</guid></item><item><title>Switzerland lets the rich tax themselves</title><link>https://julianwachholz.dev/rich-self-taxing/</link><description>&lt;p&gt;People are vain. Some have higher tendencies towards vanity than others.
This fits nicely into the scheme that the road traffic office of the canton of Zürich has been doing for years.&lt;/p&gt;
&lt;p&gt;You can bid on vanity plates for your car. These often go into the high five-figures. The auction winners and new license plate owners are not allowed to sell them freely, you may only pass them down to your children.&lt;/p&gt;
&lt;p&gt;This is effectively a self-imposed tax for well-imbued individuals.&lt;/p&gt;</description><author>Julian Wachholz</author><pubDate>Tue, 24 Sep 2024 09:45:54 GMT</pubDate><guid isPermaLink="true">https://julianwachholz.dev/rich-self-taxing/</guid></item><item><title>Generating random strings in Bash</title><link>https://heitorpb.github.io/bla/random-strings-bash/</link><description>How to generate random strings in Bash.</description><author>Heitor's log</author><pubDate>Tue, 24 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://heitorpb.github.io/bla/random-strings-bash/</guid></item><item><title>the soundless interstitium</title><link>https://taylor.town/soundless</link><description>Programmers sometimes care about correctness.</description><author>taylor.town</author><pubDate>Tue, 24 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/soundless</guid></item><item><title>Datenspuren 24</title><link>https://evilcookie.de/datenspuren-24.html</link><description/><author>blog</author><pubDate>Mon, 23 Sep 2024 22:21:40 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/datenspuren-24.html</guid></item><item><title>Exegesis: Five Albums to Meditate To</title><link>https://superbowl.substack.com/p/exegesis-five-albums-to-meditate</link><description>Mind-altering music from Laurie Anderson, Tame Impala, and more</description><author>Superb Owl</author><pubDate>Mon, 23 Sep 2024 19:46:38 GMT</pubDate><guid isPermaLink="true">https://superbowl.substack.com/p/exegesis-five-albums-to-meditate</guid></item><item><title>Updates in blog</title><link>https://prashamhtrivedi.in/updates_0924/</link><description>After more than a year, the blog is getting an update. This is a short summary of what is getting changed</description><author>Prasham H Trivedi</author><pubDate>Mon, 23 Sep 2024 17:22:37 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/updates_0924/</guid></item><item><title>How does AI impact my jobs</title><link>https://prashamhtrivedi.in/ai_impact_jobs/</link><description>(via)
It’s not their fault. They enrolled in a master’s program to get a job in tech. Why? Let’s be candid: tech has promised job security and agreeable (sometimes borderline perverse) financial returns for a couple of decades. Many tech employers also spin a yarn about “saving the world” and coast on the reputational allure of “if you work here, you’re a genius.” Sounds great, doesn’t it? Except, now that students have invested five figures of money in their tech education1, had their skulls crammed full of ‘invisible hand of the market’ propaganda, and counted on having secured their ticket to the party, they’re seeing layoffs.</description><author>Prasham H Trivedi</author><pubDate>Mon, 23 Sep 2024 16:36:13 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/ai_impact_jobs/</guid></item><item><title>Visualizing and Comparing Embedding Vectors as Heatmaps (Videos)</title><link>https://tanelpoder.com/posts/visualizing-embedding-vectors-as-heatmaps-videos/</link><description>&lt;p&gt;Following my earlier &lt;a href="https://tanelpoder.com/posts/visualizing-embedding-vectors-as-heatmaps/"&gt;Visualizing and Comparing Embedding Vectors as Heatmaps&lt;/a&gt; article, here are the launch &amp;amp; demo videos. You might want to read this article first, these short demo videos should make more sense then!&lt;/p&gt;
&lt;h2 id="visualizing-vision-transformer-embedding-vectors-as-heatmaps"&gt;Visualizing Vision Transformer Embedding Vectors as HeatMaps&lt;/h2&gt;
&lt;p&gt;In this video I&amp;rsquo;ll peek around cat vectors and then find outliers from both the cat photo dataset and aircraft dataset.&lt;/p&gt;
&lt;div class="embed video-player"&gt;
  
  
&lt;/div&gt;

&lt;h2 id="installing-catbench-vector-visualizer-app-and-technical-details"&gt;Installing CatBench Vector Visualizer App and Technical Details&lt;/h2&gt;
&lt;div class="embed video-player"&gt;
  
  
&lt;/div&gt;

&lt;br /&gt;
&lt;p&gt;The CatBench GitHub repo is here:&lt;/p&gt;</description><author>Tanel Poder Blog</author><pubDate>Mon, 23 Sep 2024 16:32:27 GMT</pubDate><guid isPermaLink="true">https://tanelpoder.com/posts/visualizing-embedding-vectors-as-heatmaps-videos/</guid></item><item><title>Generated Web Apps</title><link>https://paul.kinlan.me/generated-web-apps/</link><description>This blog post lists various web apps I've generated using Repl.it and WebSim,</description><author>Modern Web Development with Chrome</author><pubDate>Mon, 23 Sep 2024 10:53:00 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/generated-web-apps/</guid></item><item><title>Picking a Text Editor is a big deal</title><link>https://iam.mt/picking-a-text-editor-is-a-big-deal/</link><description>Now that Sublime Text 4 has asked me to upgrade its license, I am officially in the marked to try out a new text editor since it doesn't seem to have official support from a few new pieces of tech.</description><author>Mohnish Thallavajhula</author><pubDate>Mon, 23 Sep 2024 03:47:03 GMT</pubDate><guid isPermaLink="true">https://iam.mt/picking-a-text-editor-is-a-big-deal/</guid></item><item><title>What I tell people new to on-call</title><link>https://ntietz.com/blog/what-i-tell-people-new-to-oncall/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;The first time I went on call as a software engineer, it was exciting—and ultimately traumatic.
Since then, I've had on-call experiences at multiple other jobs and have grown to really appreciate it as part of the role.
As I've progressed through my career, I've gotten to help establish on-call processes and run some related trainings.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Comic showing a variety of job titles and joke interpretations of them" src="https://ntietz.com/images/comics/3-oncology.png" title="Oncology might not be what it sounds like, but Steve Ballmer did say that Linux is a cancer" /&gt;&lt;/p&gt;
&lt;p&gt;Here is some of what I wish I'd known when I started my first on-call shift, and what I try to tell each engineer before theirs.&lt;/p&gt;
&lt;h1 id="heroism-isn-t-your-job-triage-is"&gt;Heroism isn't your job, triage is&lt;/h1&gt;
&lt;p&gt;It's natural to feel a &lt;em&gt;lot&lt;/em&gt; of pressure with on-call responsibilities.
You have a production application that &lt;em&gt;real people&lt;/em&gt; need to use!
When that pager goes off, you want to go in and fix the problem yourself.
That's the job, right?&lt;/p&gt;
&lt;p&gt;But it's not.
It's not your job to fix every issue by yourself.
It &lt;em&gt;is&lt;/em&gt; your job to see that issues &lt;em&gt;get addressed&lt;/em&gt;.
The difference can be subtle, but important.&lt;/p&gt;
&lt;p&gt;When you get that page, your job is to assess what's going on.
A few questions I like to ask are:
What systems are affected?
How badly are they impacted?
Does this affect users?&lt;/p&gt;
&lt;p&gt;With answers to those questions, you can figure out what a good course of action is.
For simple things, you might just fix it yourself!
If it's a big outage, you're putting on your incident commander hat and paging other engineers to help out.
And if it's a false alarm, then you're putting in a fix for the noisy alert!
(You're going to fix it, not just ignore that, right?)&lt;/p&gt;
&lt;p&gt;Just remember not to be a hero.
You don't need to fix it alone, you just need to figure out what's going on and get a plan.&lt;/p&gt;
&lt;h1 id="call-for-backup"&gt;Call for backup&lt;/h1&gt;
&lt;p&gt;Related to the previous one, you aren't going this alone.
Your main job in holding the pager is to assess and make sure things get addressed.
Sometimes you can do that alone, but often you can't!&lt;/p&gt;
&lt;p&gt;Don't be afraid to call for backup.
People want to be helpful to their teammates, and they want that support available to them, too.
And it's better to be wake me up a little too much than to let me sleep through times when I was truly needed.
If people are getting woken up a lot, the issue isn't calling for backup, it's that you're having too many true emergencies.&lt;/p&gt;
&lt;p&gt;It's best to figure out that you need backup early, like 10 minutes in, to limit the damage of the incident.
The faster you figure out other people are needed, the faster you can get the situation under control.&lt;/p&gt;
&lt;h1 id="communicate-a-lot"&gt;Communicate a lot&lt;/h1&gt;
&lt;p&gt;In any incident, adrenaline runs and people are stressed out.
The key to good incident response is communication in &lt;em&gt;spite&lt;/em&gt; of the adrenaline.
Communicating under pressure is a skill, and it's one you can learn.&lt;/p&gt;
&lt;p&gt;Here are a few of the times and ways of communicating that I think are critical:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;When you get on and respond to an alert, say that you're there and that you're assessing the situation&lt;/li&gt;
&lt;li&gt;Once you've assessed it, post an update; if the assessment is taking a while, post updates every 15 minutes while you do so (and call for backup)&lt;/li&gt;
&lt;li&gt;After the situation is being handled, update key stakeholders at least every 30 minutes for the first few hours, and then after that slow down to hourly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You are also going to have to communicate within the response team!
There might be a dedicated incident channel or one for each incident.
Either way, try to over communicate about what you're working on and what you've learned.&lt;/p&gt;
&lt;h1 id="keep-detailed-notes-with-timestamps"&gt;Keep detailed notes, with timestamps&lt;/h1&gt;
&lt;p&gt;When you're debugging weird production stuff at 3am, that's the time you &lt;em&gt;really&lt;/em&gt; need to externalize your memory and thought processes into a notes document.
This helps you keep track of what you're doing, so you know which experiments you've run and which things you've ruled out as possibilities or determined as contributing factors.
It also helps when someone else comes up to speed!
That person will be able to use your notes to figure out what has happened, instead of you having to repeat it every time someone gets on.
Plus, the notes doc won't forget things, but you will.&lt;/p&gt;
&lt;p&gt;You will also need these notes later to do a post-mortem.
What was tried, what was found, and how it was fixed are all crucial for the discussion.
Timestamps are critical also for understanding the timeline of the incident and the response!&lt;/p&gt;
&lt;p&gt;This document should be in a shared place, since people will use it when they join the response.
It doesn't need to be shared outside of the engineering organization, though, and likely should not be.
It may contain details that lead to more questions than they answer; sometimes, normal engineering things can seem really scary to external stakeholders!&lt;/p&gt;
&lt;h1 id="you-will-learn-a-lot"&gt;You will learn a lot!&lt;/h1&gt;
&lt;p&gt;When you're on call, you get to see things break in weird and unexpected ways.
And you get to see how other people handle those things!
Both of these are great ways to learn a lot.&lt;/p&gt;
&lt;p&gt;You'll also just get exposure to things you're not used to seeing.
Some of this will be areas that you don't usually work in, like ops if you're a developer, or application code if you're on the ops side.
Some more of it will be business side things for the impact of incidents.
And some will be about the psychology of humans, as you see the logs of a user clicking a button fifteen hundred times (get that person an esports sponsorship, geez).&lt;/p&gt;
&lt;p&gt;My time on call has led to a lot of my professional growth as a software engineer.
It has dramatically changed how I worked on systems.
I don't want to wake up at 3am to fix my bad code, and I don't want it to wake you up, either.&lt;/p&gt;
&lt;p&gt;Having to respond to pages and fix things will teach you all the ways they &lt;em&gt;can&lt;/em&gt; break, so you'll write more resilient software that &lt;em&gt;doesn't&lt;/em&gt; break.
And it will teach you a lot about the structure of your engineering team, good or bad, in how it's structured and who's responding to which things.&lt;/p&gt;
&lt;h1 id="learn-by-shadowing"&gt;Learn by shadowing&lt;/h1&gt;
&lt;p&gt;No one is born skilled at handling production alerts.
You gain these skills by &lt;em&gt;doing&lt;/em&gt;, so get out there and do it—but first, watch someone else do it.&lt;/p&gt;
&lt;p&gt;No matter how much experience you have writing code (or responding to incidents), you'll learn a lot by watching a skilled coworker handle incoming issues.
Before you're the primary for an on-call shift, you should shadow someone for theirs.
This will let you see how they handle things and what the general vibe is.&lt;/p&gt;
&lt;p&gt;This isn't easy to do!
It means that they'll have to make sure to loop you in even when blood is pumping, so you may have to remind them periodically.
You'll probably miss out on some things, but you'll see a lot, too.&lt;/p&gt;
&lt;h1 id="some-things-can-and-should-wait-for-monday-morning"&gt;Some things can (and should) wait for Monday morning&lt;/h1&gt;
&lt;p&gt;When we get paged, it usually feels like a crisis.
If not to us, it sure does to the person who's clicking that button in frustration, generating a ton of errors, and somehow causing my pager to go off.
But not all alerts are created equal.&lt;/p&gt;
&lt;p&gt;If you assess something and figure out that it's only affecting one or two customers in something that's not time sensitive, and it's currently 4am on a Saturday?
Let people know your assessment (and how to reach you if you're wrong, which you could be) and &lt;em&gt;go back to bed&lt;/em&gt;.
Real critical incidents have to be fixed right away, but some things really need to wait.&lt;/p&gt;
&lt;p&gt;You want to let them go until later for two reasons.
First is just the quality of the fix.
You're going to fix things more completely if you're rested when you're doing so!
Second, and more important, is your health.
It's &lt;em&gt;wrong&lt;/em&gt; to sacrifice your health (by being up at 4am fixing things) for something non-critical.&lt;/p&gt;
&lt;h1 id="don-t-sacrifice-your-health"&gt;Don't sacrifice your health&lt;/h1&gt;
&lt;p&gt;Many of us have had bad on-call experiences.
I sure have.
One regret is that I didn't quit that on-call experience sooner.&lt;/p&gt;
&lt;p&gt;I don't even necessarily mean quitting the &lt;em&gt;job&lt;/em&gt;, but pushing back on it.
If I'd stood up for myself and said "hey, we have five engineers, it should be more than just me on call," and held firm, maybe I'd have gotten that!
Or maybe I'd have gotten a new job.
What I &lt;em&gt;wouldn't&lt;/em&gt; have gotten is the knowledge that you can develop a rash from &lt;em&gt;being too stressed&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;If you're in a bad on-call situation, please try to get out of it!
And if you can't get out of it, try to be kind to yourself and protect yourself however you can (you deserve better).&lt;/p&gt;
&lt;h1 id="be-methodical-and-reproduce-before-you-fix"&gt;Be methodical and reproduce before you fix&lt;/h1&gt;
&lt;p&gt;Along with taking great notes, you should make sure that you test hypotheses.
What could be causing this issue?
And before that, what even &lt;em&gt;is&lt;/em&gt; the problem?
And how do we make it happen?&lt;/p&gt;
&lt;p&gt;Write down your answers to these!
Then go ahead and try to reproduce the issue.
After reproducing it, you can try to go through your hypotheses and test them out to see what's actually contributing to the issue.&lt;/p&gt;
&lt;p&gt;This way, you can bisect problem spaces instead of just eliminating one thing at a time.
And since you know how to reproduce the issue now, you can be confident that you do have a fix at the end of it all!&lt;/p&gt;
&lt;h1 id="have-fun"&gt;Have fun&lt;/h1&gt;
&lt;p&gt;Above all, the thing I want people new to on-call to do?
Just have &lt;em&gt;fun&lt;/em&gt;.
I know this might sound odd, because being on call is a big job responsibility!
But I really do think it can be fun.&lt;/p&gt;
&lt;p&gt;There's a certain kind of joy in going through the on-call response together.
And there's a fun exhilaration to it all.
And the joy of fixing things and really being the competent engineer who handled it with grace under pressure.&lt;/p&gt;
&lt;p&gt;Try to make some jokes (at an appropriate moment!) and remember that whatever happens, it's &lt;em&gt;going to be okay&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Probably.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 23 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/what-i-tell-people-new-to-oncall/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>ghidra-delinker-extension is snowballing out of control</title><link>https://boricj.net/2024/09/23/ghidra-delinker-extension-snowballing-out-of-control.html</link><description>As the lack of updates might suggest, I’ve taken a break from my Tenchu: Stealth Assassins reverse-engineering/decompilation project. There’s a bunch of reasons for that and some of them are possibly relevant for whatever readership I have, so I figured I might as well write about them here.</description><author>boricj’s entropy-increasing blog</author><pubDate>Mon, 23 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boricj.net/2024/09/23/ghidra-delinker-extension-snowballing-out-of-control.html</guid></item><item><title>Be someone who does things</title><link>http://notes.eatonphil.com/2024-09-23-be-someone-who-does-things.html</link><description>&lt;p&gt;I &lt;a href="https://notes.eatonphil.com/2024-08-24-obsession.html"&gt;wrote last
month&lt;/a&gt; that
&lt;em&gt;what you want to do&lt;/em&gt; is one of the most useful motivations in life. I
want to follow that up by saying that the only thing more important
than wanting to do something is to &lt;em&gt;actually&lt;/em&gt; do something.&lt;/p&gt;
&lt;p&gt;The most valuable trait you can develop for yourself is to be
consistent. It is absolutely something you can develop. And moreover
it's kind of hard to believe that for anyone it is innate.&lt;/p&gt;
&lt;p&gt;I meet so many people who say they want to do things. And I ask them
what they're doing to get there and they get flustered. This is
completely understandable.&lt;/p&gt;
&lt;p&gt;I meet so many students who feel overwhelmed by what everyone else is
doing. This is also understandable.&lt;/p&gt;
&lt;p&gt;But it doesn't matter what anyone else is doing. It doesn't matter
where anyone else is at. It matters where you are at. Compete with
yourself before you compete with anyone else. What matters is that you
get into a habit of consistently working on little goals.&lt;/p&gt;
&lt;p&gt;If you pick something that is too complex, break it down. Keep on
breaking problems or ideas down until you find a problem or idea you
can solve.&lt;/p&gt;
&lt;p&gt;Then keep on finding new problems to solve. Move on in complexity over
time as you can and want to.&lt;/p&gt;
&lt;p&gt;Don't worry about getting things perfect. Who can discredit you for
doing your best? What shame is there when you're being earnest? The
only thing that makes sense to feel bad about is not &lt;em&gt;trying to do&lt;/em&gt;
what you &lt;em&gt;genuinely wanted to do&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And this doesn't have to be about projects or ideas outside of
work. There may be things you want to do at work like improving
documentation or writing better tests or adding new checks to code or
blogging or interviewing customers or working with another team.&lt;/p&gt;
&lt;p&gt;Like I said in
&lt;a href="https://notes.eatonphil.com/2024-08-24-obsession.html"&gt;Obsession&lt;/a&gt;,
don't worry about what you do daily. That is too frequent to think
about. Instead think about what you're doing once a month.&lt;/p&gt;
&lt;p&gt;Make time once a month to publish a post or complete a small
project. Whatever you want to do, I am confident you can find some
small version of it that you could commit to doing once a month. Be
consistent!&lt;/p&gt;
&lt;p&gt;If a month is too often, pick a longer freqency. Find whatever cadence
and whatever size of project that allows you to be consistent.&lt;/p&gt;
&lt;p&gt;When you're consistent over the course of months I think you'll be
astounded at what you accomplish in a year.&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;Shorter post tonight, may add to this later on.&lt;br /&gt;&lt;br /&gt;Be someone who does things. And do these (little) things consistently.&lt;a href="https://t.co/oVb6Sz8eEK"&gt;https://t.co/oVb6Sz8eEK&lt;/a&gt; &lt;a href="https://t.co/kNrZQ4pvTN"&gt;pic.twitter.com/kNrZQ4pvTN&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1838378171005128910?ref_src=twsrc%5Etfw"&gt;September 24, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Mon, 23 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-09-23-be-someone-who-does-things.html</guid></item><item><title>Toulky po Evropě – DiscoverEU</title><link>https://chamik.eu/2024/09/23/discover-eu/</link><description>Zápisky z cesty po Berlíně, Bruselu, Lucemburku a Métách</description><author>Kubíkovo</author><pubDate>Mon, 23 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://chamik.eu/2024/09/23/discover-eu/</guid></item><item><title>Rails on OpenBSD: Base System</title><link>https://www.gregnavis.com/articles/rails-on-openbsd-base-system.html</link><description>First there was chaos, then came UNIX.</description><author>Greg Navis</author><pubDate>Mon, 23 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://www.gregnavis.com/articles/rails-on-openbsd-base-system.html</guid></item><item><title>Automatic Speech Recognition for Maltese - Part 1</title><link>https://simonam.dev/asr-for-maltese-01/</link><description>As part of my Master’s Degree in AI, one of my assignments was to fine-tune the OpenAI Whisper model for Maltese. Maltese is a low-resource…</description><author>Simon's Blog</author><pubDate>Mon, 23 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://simonam.dev/asr-for-maltese-01/</guid></item><item><title>Unfair Data Moats &amp;amp; Regulatory Capture</title><link>https://magis.substack.com/p/unfair-data-moats-and-regulatory</link><description>How data licensing and government data can go wrong</description><author>Magis</author><pubDate>Sun, 22 Sep 2024 22:52:51 GMT</pubDate><guid isPermaLink="true">https://magis.substack.com/p/unfair-data-moats-and-regulatory</guid></item><item><title>Move Fast &amp;amp; Document Things</title><link>https://olshansky.info/posts/move-fast-document-things/</link><description>Actionable tips for software teams to build sustainable codebases of any size and reduce day-to-day frustrations</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 22 Sep 2024 22:45:01 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/move-fast-document-things/</guid></item><item><title>An Introduction to OHLC Data in Python</title><link>https://blog.adnansiddiqi.me/an-introduction-to-ohlc-data-in-python/</link><description>&lt;p&gt;This post is part of the T4P Series. In this post, we will be discussing OHLC data. OHLC is the abbreviation of Open, High, Low, and Close. We will discuss its working, its importance, and how to access it. So let&amp;#8217;s start. What is OHLC Data OHLC data is a common way to represent the price movement of an asset, whether it&amp;#8217;s a stock, cryptocurrency, or commodity, during a specific time frame (like an hour, a day, or a week). These four values give you a snapshot of how an asset&amp;#8217;s price has fluctuated during that time frame. Let&amp;#8217;s break it down: Open: This is the price at which the asset started trading when the time frame began. Imagine you’re tracking a stock from the moment the market opens—this is the price at the very beginning. High: This is the highest price the asset reached during the selected time frame. Think of it as the peak price before it dipped again. Low: This is the lowest point the asset&amp;#8217;s price dropped to during the time frame. It’s like the lowest dip before it climbed back up. Close: This is the price of the asset at the end of the time frame. It&amp;#8217;s the last price before the market closed or the period ended. Each OHLC entry represents the price movement within a specific timeframe. So, if we are looking at the hourly OHLC data for a stock (like ABC), each closing value will correspond to the last minute of that hour. For example, if the market opens at 9:00 AM, the closing values will be recorded at 10:00 AM, 11:00 AM, and 12:00 PM. OHLC data is often visualized using candlestick charts, where each candlestick represents an OHLC entry for a chosen timeframe. Traders commonly use timeframes like 1 minute, 5 minutes, 15 minutes, 30 minutes, 1 hour, 4 hours, daily, weekly, or monthly. The choice of timeframe depends on the trader&amp;#8217;s strategy—whether they are looking for short-term trades or planning to hold their position for months or even years. These timeframes help traders make informed decisions on when to enter or exit a trade based on their goals. OHLC Data Presentation Reading OHLC data as it is is not an easy task therefore traders depend on a type of chart called OHLC Chart. Each entry on that chart is called a candlestick. Each candle represents a price range, that is: what was the opening price, how high it went, how much it decreased, and at what price it was closed, in a given timeframe. In the chart above, you&amp;#8217;ll notice two types of candles: bullish and bearish. A bullish candle forms when the closing price is higher than the opening price, indicating upward momentum. In contrast, a bearish candle forms when the closing price is lower than the opening price, signaling a price decline. In this chart, green represents a bullish candle, and red represents a bearish candle. However, these colors aren’t standardized—some charts may use white and black, where black represents a bearish candle. You might also come across hollow candles, but they all represent the same concept: showing price movement within a given timeframe. The choice of color or style is just a matter of preference. Why OHLC Data is Important OHLC data is like a cheat sheet for traders and analysts, offering a snapshot of how an asset&amp;#8217;s price moves within a specific timeframe. OHLC data is key for spotting trends in the market. For instance, if the closing price consistently goes higher than the opening price over several timeframes, it might signal an upward trend, indicating that more buyers are pushing the price up. OHLC data also helps traders identify entry and exit points. If you notice that the price has dropped significantly (the Low), but then closes higher, you might consider entering a trade because the asset could be gaining strength. On the flip side, if the price spikes and then closes lower than the opening price, it could be a sign to sell before the price drops further. Lastly, OHLC data is great for understanding market volatility—how much the price is bouncing up and down. In short, OHLC data provides a well-rounded view of how an asset’s price is behaving, helping traders make smarter decisions when it comes to buying, selling, or holding. Accessing OHLC Data in Python OHLC data is typically provided by trading exchanges, but you can also access it through various third-party services. For U.S. stock data, you can use platforms like Yahoo Finance, Alpha Vantage, IEX Cloud, Interactive Brokers, and Quandl. Cryptocurrency data can be obtained from sources such as Coinbase, Binance, ByBit, and others. Forex data is available through OANDA, Quandl, Finnhub, and similar services. For commodity data, Quandl, MarketWatch, and the U.S. Energy Information Administration (EIA) are good options. These services offer both historical and real-time OHLC data to meet your needs. We will use various libraries for different purposes, with yfinance being a common choice for accessing stock data, though it&amp;#8217;s not the only option. Below is a code snippet demonstrating how to use the yfinance library to retrieve one year of data for Microsoft (MSFT): import yfinance as yf # Define the stock symbol for Microsoft symbol = 'MSFT' # Fetch historical OHLC data for Microsoft data = yf.download(symbol, start='2023-01-01', end='2024-01-01') # Display the first few rows of the data print(data.head()) If you run this code you will see the following: In technical analysis, the columns Date, Open, High, Low, Close, and Volume are commonly used. Among these, Date, Close, and Volume are particularly important, as they are frequently utilized in various technical indicators and analysis techniques. Conclusion I hope after reading this post, you now have a better idea of what’s coming next. Understanding OHLC data is crucial because it forms the foundation of technical analysis. Without it, analyzing price movements and making informed trading decisions would be much more difficult. If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/an-introduction-to-ohlc-data-in-python/"&gt;An Introduction to OHLC Data in Python&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Sun, 22 Sep 2024 22:00:50 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/an-introduction-to-ohlc-data-in-python/</guid></item><item><title>Cloud GPU Services for Deep Learning and fine-tuning with Jupyter Notebooks Reviewed: Colab, Paperspace Gradient, Lightning.ai, and more</title><link>https://zackproser.com/blog/cloud-gpu-services-jupyter-notebook-reviewed</link><description>I tried a handful of services when I last needed to fine-tune an LLM, and I was mostly disappointed...</description><author>Zachary Proser</author><pubDate>Sun, 22 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/cloud-gpu-services-jupyter-notebook-reviewed</guid></item><item><title>How to create a custom Alpaca instruction dataset for fine-tuning LLMs</title><link>https://zackproser.com/blog/how-to-create-a-custom-alpaca-dataset</link><description>A step by step tutorial with companion notebook.</description><author>Zachary Proser</author><pubDate>Sun, 22 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/how-to-create-a-custom-alpaca-dataset</guid></item><item><title>How to Fine-tune Llama 3.1 on Lightning.ai with Torchtune</title><link>https://zackproser.com/blog/how-to-fine-tune-llama-3-1-on-lightning-ai-with-torchtune</link><description>One of the better Jupyter Notebooks to GPU-backed environment experiences I've had...</description><author>Zachary Proser</author><pubDate>Sun, 22 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/how-to-fine-tune-llama-3-1-on-lightning-ai-with-torchtune</guid></item><item><title>MLOps Adventure - Learning to Fine-tune LLMs, create datasets and neural nets</title><link>https://zackproser.com/blog/mlops-adventure</link><description>I've been on an MLOps adventure lately, taking any excuse to get hands on with neural nets, fine-tuning, Hugging Face datasets and models.</description><author>Zachary Proser</author><pubDate>Sun, 22 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/mlops-adventure</guid></item><item><title>The Rich Don't Fine-tune Like You and Me: Intro to LoRA and QLoRA</title><link>https://zackproser.com/blog/what-is-lora-and-qlora</link><description>LoRA and QLoRA are two important innovations related to fine-tuning large language models like Llama and GPT.</description><author>Zachary Proser</author><pubDate>Sun, 22 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/what-is-lora-and-qlora</guid></item><item><title>The sorry state of Java deserialization</title><link>https://www.marginalia.nu/log/a_110_java_io/</link><description>&lt;p&gt;I&amp;rsquo;ve been on a bit of a frustration-driven quest to solve a problem I frequently encounter
working on the search engine, that is, reading data from disk.&lt;/p&gt;
&lt;p&gt;You&amp;rsquo;d think this would be a pretty basic thing, but doing this in a way that is half-way performant is surprisingly hard and requires avoiding basically all the high level tools at your disposal.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s a common sentiment that modern hardware is fast, so this may not matter, but we aren&amp;rsquo;t speaking a 30% performance hit, the the question is how many orders of magnitude you&amp;rsquo;re willing to forego.&lt;/p&gt;</description><author>Weblog on marginalia.nu</author><pubDate>Sun, 22 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.marginalia.nu/log/a_110_java_io/</guid></item><item><title>Irresponsible Servers: From Slop Talk to Shop Talk</title><link>https://taylor.town/irresponsible-servers</link><description>400: OUT OF STOCK</description><author>taylor.town</author><pubDate>Sun, 22 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/irresponsible-servers</guid></item><item><title>How I manage my 1v1s</title><link>https://bytepawn.com/how-i-manage-my-1v1s.html</link><description>&lt;p&gt;I detail my structured approach to managing one-on-one meetings within a 40-person data team, emphasizing people management principles like radical transparency, tailored meeting cadences, and strategies to navigate common managerial challenges to foster a supportive and productive work environment. &lt;br /&gt;&lt;br /&gt; &lt;img alt="Meeting distribution" src="/images/meeting-distribution.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 22 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/how-i-manage-my-1v1s.html</guid></item><item><title>AI code generation as an agent of tech debt creation</title><link>https://greaterdanorequalto.com/ai-code-generation-as-an-agent-of-tech-debt-creation/</link><description>I've disabled all LLM-based AI Assistants/Copilots/whatever-you-call-'ems in my IDE.</description><author>Greater Dan Or Equal To</author><pubDate>Sat, 21 Sep 2024 23:01:24 GMT</pubDate><guid isPermaLink="true">https://greaterdanorequalto.com/ai-code-generation-as-an-agent-of-tech-debt-creation/</guid></item><item><title>Turning suffering into growth</title><link>https://dostoynikov.bearblog.dev/turning-suffering-into-growth/</link><description>&lt;p&gt;I'm definitely not a fatalistic person. I do not believe in a predetermined destiny, but I accept whatever life throws at me. When something bad happens, I fully embrace it. This does not mean I don't make an effort to fix the problem, but I never lose my calm. I acknowledge and embrace it. I don't let it consume my mind or stress me out. I'm always aware that what has happened cannot be undone. I also know that without suffering, there is no spiritual growth. But how can we embrace suffering in a healthy way? While not being fatalistic, I still need to accept fate in a way.&lt;/p&gt;
&lt;p&gt;Nietzsche said:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"I want to learn more and more to see as beautiful what is necessary in things; then I shall be one of those who make things beautiful. Amor fati: let that be my love henceforth! I do not want to wage war against what is ugly. I do not want to accuse; I do not even want to accuse those who accuse. Looking away shall be my only negation. And all in all, and on the whole: some day I wish to be only a Yes-sayer."&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This world is a constant battleground. People reveal their strengths and potentials by overcoming the pain and challenges they face in life. Life is a struggle and a constant process of transformation. In this struggle, human suffering is inevitable, but this suffering should be seen as a tool for growth and development.&lt;/p&gt;
&lt;p&gt;There are times I feel very weak and full of despair. Until today, I have somehow overcome my struggles with this mindset, and hopefully, it will continue to work for the rest of my life.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I believe that the ultimate peace will only come when I enter the grave.&lt;/em&gt;&lt;/p&gt;</description><author>ᓚᘏᗢdostoynikov</author><pubDate>Sat, 21 Sep 2024 22:13:00 GMT</pubDate><guid isPermaLink="true">https://dostoynikov.bearblog.dev/turning-suffering-into-growth/</guid></item><item><title>Alvin's Ramp</title><link>http://www.nuke24.net/plog/48.html</link><author>TOGoS's Project Log</author><pubDate>Sat, 21 Sep 2024 21:13:37 GMT</pubDate><guid isPermaLink="true">http://www.nuke24.net/plog/48.html</guid></item><item><title>Arrogant programmers and Enshittification - A New Understanding</title><link>https://mikewarot.blogspot.com/2024/09/arrogant-programmers-and.html</link><description>&lt;p&gt;
  A few weeks ago, I
  &lt;a href="https://news.ycombinator.com/item?id=41265143"&gt;replied&lt;/a&gt; to
  &lt;a href="https://news.ycombinator.com/item?id=41263143" target="_blank"&gt;a thread on Hacker News&lt;/a&gt;
  about a "leaked" talk by former Google CEO&amp;nbsp;&amp;nbsp;Eric Schmidt. The key
  phrase I tried to pick apart was
&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
  So imagine a non-arrogant programmer that actually does what you want
&lt;/blockquote&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;
  I &lt;i&gt;had assumed&lt;/i&gt; he was referring to situations similar to my own
  experience as a software developer interacting with customers back in the
  1980s. Programming was still a relatively new thing, and most people didn't
  understand how data was stored and processed. I considered it my job to probe
  what they really wanted to have, and figure out how I could meet their needs
  by
  &lt;a href="https://en.wikipedia.org/wiki/Impedance_matching" target="_blank"&gt;impedance matching&lt;/a&gt;
  them to what was feasible. This smoothing over of their wants and needs with
  reality made everyone happy, in the end.
&lt;/p&gt;
&lt;hr /&gt;A few days ago, I watched &lt;a href="https://www.youtube.com/watch?v=4EmstuO0Em8" target="_blank"&gt;Cory Doctorow's presentation at DefCon32&lt;/a&gt;
about the ongoing&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Enshittification" target="_blank"&gt;Enshittification&lt;/a&gt;&amp;nbsp;of everything. He delivered, as usual for him, a passionate and fact
stuffed critique of the way things are going, and how we got there. It's been
quietly flowing through my
&lt;a href="http://www.programmersstone.com/" rel="nofollow" target="_blank"&gt;mapper mind&lt;/a&gt;, bumping up against all of the other chunks of knowledge I've recently
pondered.
&lt;div&gt;
  &lt;hr /&gt;
  &lt;p&gt;
    This morning, I was reading a
    &lt;a href="https://news.ycombinator.com/item?id=41609099" target="_blank"&gt;thread&lt;/a&gt;
    about Thea Lim's
    &lt;a href="https://thewalrus.ca/collapse-of-self-worth-in-the-digital-age/" target="_blank"&gt;The Collapse of Self-Worth in the Digital Age&lt;/a&gt;.&amp;nbsp;
  &lt;/p&gt;
  &lt;p&gt;She helped me see that though I have had my self-worth tied up in work, there was a deep cause of disastisfaction once I was out of the work force. I've always been good at explaining complex things in a way others can understand. (Impedance matching, again!)

  This bumped against some other things I was pondering, and &lt;b&gt;then the penny dropped&lt;/b&gt;
  &lt;/p&gt;
  &lt;p&gt;&lt;i&gt;I was wrong about what Eric Schmidt meant, and I see that now.&lt;/i&gt;&lt;/p&gt;
  &lt;p&gt;
    I had made a
    &lt;a href="https://en.wikipedia.org/wiki/Category_mistake" target="_blank"&gt;category error&lt;/a&gt;
    and assumed he was just trying to get good results, or meet some weird quirk
    of his design sense, like Steve Jobs is said to have done, and was getting
    frustrated. Oh boy, was I ever wrong.&lt;br /&gt;&lt;br /&gt;Assuming that Schmidt was
    talking bout overriding ethical concerns is a way better fit for the amount
    of emotion this seems to hold for him.
  &lt;/p&gt;
  &lt;p&gt;Cory's right. We really do have to hold the line against this shit.&lt;/p&gt;
  &lt;hr /&gt;
  &lt;p&gt;So there you have it... a brief journey through the thought process of a mapper, with a side of self-discovery&lt;/p&gt;
&lt;/div&gt;</description><author>--Mike--</author><pubDate>Sat, 21 Sep 2024 20:44:39 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2024/09/arrogant-programmers-and.html</guid></item><item><title>Claude 3.5 Sonnet Connections Evals</title><link>https://www.danielcorin.com/posts/2024/claude-35-sonnet-connections-evals/</link><description>Claude 3.5 Sonnet Connections Evals</description><author>Thought Eddies</author><pubDate>Sat, 21 Sep 2024 15:53:47 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2024/claude-35-sonnet-connections-evals/</guid></item><item><title>Engineering the Infinite: Why Game Development is the Ultimate Frontier for Engineers</title><link>https://mitrapunk.com/engineering-the-infinite-why-game-development-is-the-ultimate-frontier-for-engineers/</link><description>&lt;p&gt;As engineers, we are the architects of innovation, the problem-solvers who transform ideas into reality. We design bridges that defy gravity, algorithms that decode complexity, and machines that propel humanity forward. Yet, amidst these tangible achievements, there lies a realm where the boundaries of possibility are not just pushed&amp;#x2014;&lt;/p&gt;</description><author>Mitrapunk: Engineering Game</author><pubDate>Sat, 21 Sep 2024 15:23:45 GMT</pubDate><guid isPermaLink="true">https://mitrapunk.com/engineering-the-infinite-why-game-development-is-the-ultimate-frontier-for-engineers/</guid></item><item><title>Visualizing and Comparing Embedding Vectors as Heatmaps</title><link>https://tanelpoder.com/posts/visualizing-embedding-vectors-as-heatmaps/</link><description>&lt;p&gt;&lt;em&gt;An interactive demo video is here: &lt;a href="https://tanelpoder.com/posts/visualizing-embedding-vectors-as-heatmaps-videos/"&gt;Part 2&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Cat embeddings heatmap" src="https://tanelpoder.com/files/images/cat-embeddings-heatmap.png" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;If you already know what embeddings are, you can jump right to:&lt;/em&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="#visualizing-embedding-vectors-as-heatmaps"&gt;Vector Heatmap Visualization&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="background"&gt;Background&lt;/h2&gt;
&lt;p&gt;I am currently testing various RDBMS vector search options and their usability &amp;amp; performance. Next week I will publish an early version of my &lt;a href="https://github.com/tanelpoder/catbench"&gt;CatBench&lt;/a&gt; toolset that will eventually evolve into a full end-to-end test (and fun!) suite for high performance AI/ML pipelines, with things like streaming image recognition using Vision Transformers on GPUs, followed by vector searches in target databases and more.&lt;/p&gt;</description><author>Tanel Poder Blog</author><pubDate>Sat, 21 Sep 2024 04:16:34 GMT</pubDate><guid isPermaLink="true">https://tanelpoder.com/posts/visualizing-embedding-vectors-as-heatmaps/</guid></item><item><title>How I batch apply and save one-liners</title><link>https://xenodium.com/how-i-batch-apply-and-save-one-liners</link><description>&lt;p&gt;My significant other needed to share proof of address by providing a number of bank statements for a period of time. That's easy enough to download as pdfs from the bank, but statements typically provide more personal information than the recipient requires. For a proof of address, the first page is more than enough.&lt;/p&gt;
&lt;p&gt;macOS's Preview app can easily delete pages from a pdf by selecting undesired pages and hitting the delete key. This is fine for one pdf but for a handful of them, I figured there's a command line incantation I could use out there, and indeed there is:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;qpdf my.pdf --pages . 1 -- my-one-page.pdf
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With command in mind, I resorted to my now my typical approach of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Convert to a generic &lt;a href="https://github.com/xenodium/dwim-shell-command"&gt;dwim-shell-command&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Batch apply.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I could be done at this point, but since I now have the command fresh in mind…&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Save command for future usage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So let's get on with it.&lt;/p&gt;
&lt;h2&gt;Converting to dwim-shell-command&lt;/h2&gt;
&lt;pre&gt;&lt;code class="language-{.bash"&gt;qpdf '&amp;lt;&amp;lt;f&amp;gt;&amp;gt;' --pages . 1 -- '&amp;lt;&amp;lt;fne&amp;gt;&amp;gt;_1.&amp;lt;&amp;lt;e&amp;gt;&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Batch apply&lt;/h2&gt;
&lt;p&gt;Other than show it in action, it may be worth mentioning dwim-shell-command recognizes files in region (in addition to dired's mark of course), so you can just select and apply.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-batch-apply-and-save-one-liners/keep-1-page.gif" /&gt;&lt;/p&gt;
&lt;h2&gt;Save for future usage&lt;/h2&gt;
&lt;p&gt;Saving these commands for future usage typically consists of merely wrapping in an Emacs command so we can invoke via M-x (and your favorite narrowing framework for that fuzzy quick magic).&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun dwim-shell-commands-keep-pdf-page ()
  &amp;quot;Keep a page from pdf.&amp;quot;
  (interactive)
  (let ((page-num (read-number &amp;quot;Keep page number: &amp;quot; 1)))
    (dwim-shell-command-on-marked-files
     &amp;quot;Keep pdf page&amp;quot;
     (format &amp;quot;qpdf '&amp;lt;&amp;lt;f&amp;gt;&amp;gt;' --pages . %d -- '&amp;lt;&amp;lt;fne&amp;gt;&amp;gt;_%d.&amp;lt;&amp;lt;e&amp;gt;&amp;gt;'&amp;quot; page-num page-num)
     :utils &amp;quot;qpdf&amp;quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For this instance, there's a tiny bit of additional logic to ask the user which page they'd like to keep.&lt;/p&gt;
&lt;p&gt;While there's no way I'll remember &lt;code&gt;qpdf my.pdf --pages . 1 -- my-one-page.pdf&lt;/code&gt;, I can easily find it in the future by searching with something like &lt;code&gt;M-x keep page&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/how-i-batch-apply-and-save-one-liners/keep-1-page-command.gif" /&gt;&lt;/p&gt;
&lt;h2&gt;My toolbox&lt;/h2&gt;
&lt;p&gt;I've saved a bunch of these commands and use many of them regularly. You can find in the &lt;a href="https://github.com/xenodium/dwim-shell-command?tab=readme-ov-file#my-toolbox"&gt;optional component of dwim-shell-command&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Enjoying this content? Using one of my Emacs packages?&lt;/h2&gt;
&lt;p&gt;Help make the work sustainable. Consider &lt;a href="https://github.com/sponsors/xenodium"&gt;sponsoring&lt;/a&gt;. I'm also building &lt;a href="https://lmno.lol/"&gt;lmno.lol&lt;/a&gt;. A platform to &lt;a href="https://indieweb.social/@xenodium/112265481282475542"&gt;drag and drop&lt;/a&gt; your blog to the web.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Sat, 21 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/how-i-batch-apply-and-save-one-liners</guid></item><item><title>Querying IP addresses and CIDR ranges with DuckDB</title><link>https://tobilg.com/posts/querying-ip-addresses-and-cidr-ranges-with-duckdb/</link><description>I had a use case that eventually required performing IP address lookups in a given list of CIDR ranges, as I maintain an open source project that gathers IP address range data f...</description><author>tobilg.com</author><pubDate>Sat, 21 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://tobilg.com/posts/querying-ip-addresses-and-cidr-ranges-with-duckdb/</guid></item><item><title>Like of https://catgirlin.space/activity/111/</title><link>https://qubyte.codes/likes/1726860619695</link><description>&lt;p&gt;Like of &lt;a href="https://catgirlin.space/activity/111/"&gt;https://catgirlin.space/activity/111/&lt;/a&gt;&lt;/p&gt;</description><author>Qubyte Codes</author><pubDate>Fri, 20 Sep 2024 22:30:19 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/likes/1726860619695</guid></item><item><title>The Next Generation Semantic Search</title><link>https://mbutler.org/the-next-generation-semantic-search/</link><description>Next Generation Semantic Search grew out of a game a friend runs on WhatsApp called Picardle. Instead of guessing words, players are shown screenshots from Star Trek: The Next Generation and try to identify the episode by describing its plot. Exact episode titles are rarely guessable, so scoring is based on semantic closeness rather than [&amp;#8230;]</description><author>mbutler</author><pubDate>Fri, 20 Sep 2024 18:30:42 GMT</pubDate><guid isPermaLink="true">https://mbutler.org/the-next-generation-semantic-search/</guid></item><item><title>Bottom-up vs top-down product management</title><link>https://ernest.oppet.it/2024/09/20/bottom-up-vs-top-down-product-management/</link><description>I like this definition of product management from Lenny&amp;#8217;s newsletter: Your job as a PM is to deliver business impact by marshaling the resources of your team to identify and solve the most impactful customer problems. I think it clearly encapsulates one of the hardest things about the job which is that it entails both [&amp;#8230;]</description><author>Ernest Oppetit</author><pubDate>Fri, 20 Sep 2024 15:46:39 GMT</pubDate><guid isPermaLink="true">https://ernest.oppet.it/2024/09/20/bottom-up-vs-top-down-product-management/</guid></item><item><title>Sink – read it later app for iPhone</title><link>https://swiftfox.co/2024/09/sink-read-it-later-app-for-iphone/</link><description>&lt;p&gt;Sink is now available for download on the App Store! Sink was built and released as part of the RevenueCat Ship-A-Ton contest. Sink is a read-it-later app with a focus on keeping all articles you have read in a fully searchable archive. It includes a built-in markdown notes editor, AI article summarization and full offline...  &lt;a class="excerpt-read-more" href="https://swiftfox.co/2024/09/sink-read-it-later-app-for-iphone/" title="ReadSink &amp;#8211; read it later app for iPhone"&gt;Read more &amp;#187;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://swiftfox.co/2024/09/sink-read-it-later-app-for-iphone/"&gt;Sink – read it later app for iPhone&lt;/a&gt; first appeared on &lt;a href="https://swiftfox.co"&gt;Swift Fox Software LLC&lt;/a&gt;.&lt;/p&gt;</description><author>Swift Fox Software LLC</author><pubDate>Fri, 20 Sep 2024 13:28:51 GMT</pubDate><guid isPermaLink="true">https://swiftfox.co/2024/09/sink-read-it-later-app-for-iphone/</guid></item><item><title>Books</title><link>https://camhashemi.com/essays/books/</link><description>&lt;p&gt;A couple years ago, my wife and I started a reading habit. Slowly but surely, we replaced watching Netflix and scanning our phones with reading before bed.&lt;/p&gt;&lt;p&gt;My sleep has drastically improved, especially when reading fiction. I also love it as a way to &amp;ldquo;warm up&amp;rdquo; in the mornings, getting some sunlight and peace before my morning coffee and kindergarten routine.&lt;/p&gt;&lt;p&gt;To share books that may help you kick-start or sustain your own reading habit, here are some brief reviews.&lt;/p&gt;&lt;h2 id="current"&gt;Current&lt;/h2&gt;&lt;p&gt;I&amp;rsquo;m currently reading &lt;em&gt;The E-Myth Revisited&lt;/em&gt; by Michael Gerber.&lt;/p&gt;&lt;p&gt;He calls out common ways that founders unnecessarily struggle when starting a small business.&lt;/p&gt;&lt;p&gt;He proposes two mental models:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Three Archetypes: the Technician, the Manager, and the Entrepreneur. Businesses &amp;amp; their owners suffer when we lack a balance across the archetypes.&lt;/li&gt;&lt;li&gt;The Franchise Prototype: a thoughtful and explicit model for how the business should work. This is the primary output of a business owner, much more so than the work itself.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Perhaps more interestingly for me is his style. It&amp;rsquo;s more top-down than bottom-up, using metaphor and appeals to common sense rather than data and case studies to &amp;ldquo;prove&amp;rdquo; the points.&lt;/p&gt;&lt;p&gt;It reminded me that my style is top-down, and that makes it hit-or-miss. If the message resonates, it highlights the essential dynamics more clearly than a bunch of examples. But if the message doesn&amp;rsquo;t resonate, it reads more like idiosyncratic rambling than insightful prose.&lt;/p&gt;&lt;h2 id="2024"&gt;2024&lt;/h2&gt;&lt;h3 id="red-notice"&gt;Red Notice&lt;/h3&gt;&lt;p&gt;After hearing that I loved &lt;em&gt;Shoe Dog&lt;/em&gt;, an old teammate recommended I read this similarly-page-turning memoir.&lt;/p&gt;&lt;p&gt;It&amp;rsquo;s about an American hedge fund manager&amp;rsquo;s story about investment, corruption, and persecution in post-Soviet Russia.&lt;/p&gt;&lt;p&gt;It delivered on its promise, and it opened my eyes to state corruption and brutality in more detail than I&amp;rsquo;d known before.&lt;/p&gt;&lt;h3 id="pragmatism"&gt;Pragmatism&lt;/h3&gt;&lt;p&gt;This is one of the box-full of books that I&amp;rsquo;ve taken with me to every new home, in the hopes that I&amp;rsquo;ll eventually read it. Some of those books may never be read, but somehow I picked this one up this year.&lt;/p&gt;&lt;p&gt;It was quite serendipitous that I did. &lt;em&gt;Pragmatism&lt;/em&gt; is a philosophy I&amp;rsquo;ve practiced for years without knowing it. Before reading the book, I&amp;rsquo;d even been working an essay which promotes evaluating our beliefs based on their usefulness to us rather than on their (often elusive) accuracy.&lt;/p&gt;&lt;p&gt;From &lt;a href="https://en.wikipedia.org/wiki/Pragmatism" rel="noopener" target="_blank"&gt;Wikipedia&lt;/a&gt;:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;Pragmatism is a philosophical tradition that views language and thought as tools for prediction, problem solving, and action, rather than describing, representing, or mirroring reality. Pragmatists contend that most philosophical topics—such as the nature of knowledge, language, concepts, meaning, belief, and science—are best viewed in terms of their practical uses and successes.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;I believe there is an objective reality that we&amp;rsquo;re embedded in, but I doubt my ability to know it accurately. I&amp;rsquo;m very confident in my ability to mistakenly believe the wrong thing. This applies less-so to clear domains, like physics, and more-so to opaque domains, like psychology, social dynamics, and the future.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Pragmatism&lt;/em&gt; was insightful, but the style was difficult. I&amp;rsquo;m motivated to find more-accessible content on Pragmatism now, but I&amp;rsquo;m not sure I&amp;rsquo;d recommend this particular book.&lt;/p&gt;&lt;h3 id="the-lean-startup"&gt;The Lean Startup&lt;/h3&gt;&lt;p&gt;&lt;em&gt;The Innovator&amp;rsquo;s Dilemma&lt;/em&gt; is captured by a few statements:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;Disruptive technologies often develop by serving small-but-promising markets.&lt;/li&gt;&lt;li&gt;As their promise overtakes their smallness, these technologies put incumbents out of business and make startups into market-leaders.&lt;/li&gt;&lt;li&gt;But small markets have a dearth of available information about their promise, so traditional analysis is counter-productive.&lt;/li&gt;&lt;li&gt;Therefore, market leaders tend to fail at identifying disruptive technologies - not to mention all the dead-end startups who never really had a chance.&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;em&gt;The Lean Startup&lt;/em&gt; is the answer to this dilemma. Rather than projecting a market&amp;rsquo;s promise through analysis, it shows us how to &lt;em&gt;discover&lt;/em&gt; a market&amp;rsquo;s promise through experimentation, learning, and adaptation.&lt;/p&gt;&lt;p&gt;Whereas market leaders are best-suited for top-down strategies based on their wealth of proprietary data, startups are best-suited for the bottom-up strategy of building, talking to customers, and adapting wherever seems promising.&lt;/p&gt;&lt;p&gt;&lt;em&gt;The Lean Startup&lt;/em&gt; is a framework for doing exactly that.&lt;/p&gt;&lt;h3 id="the-innovators-dilemma"&gt;The Innovator&amp;rsquo;s Dilemma&lt;/h3&gt;&lt;p&gt;Clayten Christensen basically defined the term &amp;ldquo;disruption&amp;rdquo;, the pattern of industry leaders to consistently fail to capture the new-and-growing markets. When those young markets take over the world, those leaders eventually become embarrassing followers &amp;ndash; assuming they survive.&lt;/p&gt;&lt;p&gt;This book is inspiring for any aspiring entrepreneurs because it makes a robust case for why market opportunities are more abundant than they appear.&lt;/p&gt;&lt;h3 id="think-and-grow-rich"&gt;Think and Grow Rich&lt;/h3&gt;&lt;p&gt;When I was around eighteen, I read &lt;em&gt;How to Win Friends and Influence People&lt;/em&gt; by Dale Carnegie, and it changed my life.&lt;/p&gt;&lt;p&gt;I am looking forward to reading that book again. I see it as a kind-of religious text, teaching us how to see positive-sum games out of zero-sum games.&lt;/p&gt;&lt;p&gt;The downside of that book is that it was written in the early 1900s, and both the title and the style are quite cringe to our modern sensibilities.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Think and Grow Rich&lt;/em&gt; is very similar in that sense: powerful for anyone who&amp;rsquo;s never been exposed to the perspective, but obscured behind an exteremely paternal writing style.&lt;/p&gt;&lt;p&gt;It&amp;rsquo;s not my favorite book, but I see a ton of overlap between it and Neurolinguistic Programming. So it can be a useful entry point into taking control of your thoughts and emotions, such that those internal forces manifest what you want externally.&lt;/p&gt;&lt;h3 id="shoe-dog"&gt;Shoe Dog&lt;/h3&gt;&lt;p&gt;Instant favorite: I never read a book so fast - 8 days!&lt;/p&gt;&lt;p&gt;After reading the first chapter, where Phil Knight describes the moment he decided to chase his Crazy Idea at twenty-four years old, I fell into a trance about my own Crazy Idea and went on a passionate sunrise run to embody that spirit.&lt;/p&gt;&lt;p&gt;I&amp;rsquo;m very grateful for that moment and can recommend the book to anyone looking to start their own business.&lt;/p&gt;&lt;h3 id="the-picture-of-dorian-gray"&gt;The Picture of Dorian Gray&lt;/h3&gt;&lt;p&gt;I plan to write an analysis of this book down the road, so won&amp;rsquo;t go too into it.&lt;/p&gt;&lt;p&gt;I see it as an exploration of Cynical Hedonism in late-1800s London society. I find it extremely applicable in today&amp;rsquo;s world, where Cynical Hedonism has an even stronger pull than it did back then.&lt;/p&gt;&lt;h3 id="brave-new-world"&gt;Brave New World&lt;/h3&gt;&lt;p&gt;After a slow start to set up its dystopia, the book takes off and was a quick and fun read.&lt;/p&gt;&lt;p&gt;It&amp;rsquo;s a clever analysis of the purpose of human life and society. I probably haven&amp;rsquo;t laughed out loud from a book as much as this one.&lt;/p&gt;&lt;h3 id="the-alchemist"&gt;The Alchemist&lt;/h3&gt;&lt;p&gt;This year I re-read one of the most important books in my life. This book inspired me to leave a relatively comfortable life to travel the world, and it made all the difference.&lt;/p&gt;&lt;p&gt;I read it again in preparation for my wedding, because the book was a critical part of my relationship in the early days.&lt;/p&gt;&lt;p&gt;I see now how much it actually shaped my worldview, and how the current chapter of my journey is a continuation of that influence.&lt;/p&gt;&lt;h3 id="antifragile"&gt;Antifragile&lt;/h3&gt;&lt;p&gt;His previous books, &lt;em&gt;Fooled by Randomness&lt;/em&gt; and &lt;em&gt;The Black Swan&lt;/em&gt;, were already instant-favorites, and &lt;em&gt;Antifragile&lt;/em&gt; topped them both.&lt;/p&gt;&lt;p&gt;Taleb is the only author I&amp;rsquo;ve found who focuses on epistemology, namely probabilistic thinking and decision-making. While his previous books were more focused on the problem,in &lt;em&gt;Antifragile&lt;/em&gt; he actually gives us a solution.&lt;/p&gt;&lt;p&gt;This book has been invaluable for me in life, software, and business.&lt;/p&gt;&lt;h2 id="2023"&gt;2023&lt;/h2&gt;&lt;h3 id="the-count-of-monte-cristo"&gt;The Count of Monte Cristo&lt;/h3&gt;&lt;p&gt;The perfect classic fiction book, about the hero&amp;rsquo;s journey of a young, innocent groom to-be, who is betrayed by his so-called friends in the worst way imaginable.&lt;/p&gt;&lt;p&gt;This book got me back into fiction, and I can highly recommend it for anyone looking for a fun read.&lt;/p&gt;&lt;p&gt;The only problem is that it&amp;rsquo;s like 1300 pages, so it took me all of 2023 to read. There are some slow parts, but they do a great jobof setting up subsequent climaxes, so I can recommend sticking with it.&lt;/p&gt;</description><author>Cam Hashemi</author><pubDate>Fri, 20 Sep 2024 10:34:56 GMT</pubDate><guid isPermaLink="true">https://camhashemi.com/essays/books/</guid></item><item><title>Highlights - Dune</title><link>https://siddhesh.substack.com/p/highlights-dune</link><description>"Fear is the mind killer..."</description><author>Obvious Bicycle</author><pubDate>Fri, 20 Sep 2024 04:33:27 GMT</pubDate><guid isPermaLink="true">https://siddhesh.substack.com/p/highlights-dune</guid></item><item><title>On this day, September 20</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-20/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2010 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Fri, 20 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-20/</guid></item><item><title>The American Dream</title><link>https://camhashemi.com/essays/american-dream/</link><description>&lt;p&gt;In a debate asking &amp;ldquo;Is the American Dream Alive?&amp;rdquo;, economist Tyler Cowen argued &amp;ldquo;yes&amp;rdquo; with the following point:&lt;/p&gt;&lt;blockquote&gt;&lt;p&gt;The American Dream, as measured through intergenerational mobility, has risen radically, numerically, as the number of immigrantswe&amp;rsquo;re taking in has increased. &amp;hellip; This is the median household income for Bolivians, most Bolivians not coming from wealthy families:$87,000 a year. Albanians, a country ravaged by communism, basically hardly anyone had wealth, median household income in the US: $85,000. Iranians: $96,000.Filipinos: $101,000. These are medians - not just a few charismatics! Indian Americans - this could be the highest number ever for any group:it is $152,000. Coming from a very, very poor country. Individuals who came to America and made better lives for themselves.&lt;/p&gt;&lt;/blockquote&gt;&lt;p&gt;Hearing Cowen implicitly reference my parents in defending the American Dream made me feel proud in a way I haven&amp;rsquo;t in a long time.&lt;/p&gt;&lt;p&gt;My dad grew up poor in Iran. He used to tell me about how he&amp;rsquo;d sometimes have to go to bed hungry, and how much he’d hate it.Despite significant obstacles growing up, he excelled at math, and was eventually given the opportunity to pursue his doctorate at UC Berkeley.He took it, left before the Islamic Revolution, and eventually dropped out to begin a career as an engineer in a budding Silicon Valley.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;At a local college many years later, I took an intro to sociology class, taught by a decent professor whose central thesis was that the American Dream is a lie.On most days, I&amp;rsquo;d raise my hand and argue with him.&lt;/p&gt;&lt;p&gt;My central thesis was that even if upwards mobility is unlikely, we as individuals need to believe we can beat the odds in order to do what&amp;rsquo;s best for ourselves and our families.&lt;/p&gt;&lt;p&gt;I only now realize Cowen&amp;rsquo;s more direct point: that I was already living out &lt;em&gt;my parents&amp;rsquo;&lt;/em&gt; American Dream.&lt;/p&gt;&lt;p&gt;Seeing that hit me hard and makes me deeply grateful for my parents and for this country.Insofar as making money reflects a contribution to society, his data honors those who did something with whatever opportunities America could afford them.&lt;/p&gt;&lt;p&gt;Relative to being born in the motherland, many of us second-generation immigrants are incredibly privileged. I honor my parents in saying that, and I wish I had done it more growing up. Back then, all I could see was the struggles I had compared to the typical American.&lt;/p&gt;&lt;p&gt;Second-generation identity is complex and was painful for me to work through, but focusing on my disadvantages missed the forest for the trees.&lt;/p&gt;&lt;p&gt;Thanks to the American Dream, I had already won the lottery the day I was born.&lt;/p&gt;&lt;hr /&gt;&lt;p&gt;My dad is a democratic socialist: his views seem most aligned with Noam Chomsky and Bernie Sanders. But when we talked politics, he&amp;rsquo;d often say that while America isn&amp;rsquo;t perfect, it&amp;rsquo;s better than anywhere else he knows.&lt;/p&gt;&lt;p&gt;We must continue to find ways to improve our country.&lt;/p&gt;&lt;p&gt;Just as importantly, the story of countless immigrants says that we should also protect it. Counter to the American Dream, there’s another dream, whose story says that we need to tear down our system before we can build something better.&lt;/p&gt;&lt;p&gt;As an engineer now myself, I&amp;rsquo;ve felt that temptation on every codebase I&amp;rsquo;ve inherited. But when that system is already serving millions of customers in both clear and opaque ways, pursuing that revolutionary dream can easily become a nightmare. This nightmare has played out time and time again in both technical and societal projects.&lt;/p&gt;&lt;p&gt;Looking at the depth and breadth of prosperity created by the current dream, in stories like mine and millions more, the data says that we are much better off with the old American Dream than a new one.&lt;/p&gt;&lt;p&gt;And we, especially us immigrants, can be grateful for that.&lt;/p&gt;</description><author>Cam Hashemi</author><pubDate>Fri, 20 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://camhashemi.com/essays/american-dream/</guid></item><item><title>On this day, September 19</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-19/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2010 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Thu, 19 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-19/</guid></item><item><title>Emacs bubble mode</title><link>https://xenodium.com/emacs-bubble-mode</link><description>&lt;p&gt;From time to time, I want to grab a source code viewport of sorts and feed to an LLM for questioning. From Emacs, I normally use &lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt;'s &lt;code&gt;chatgpt-shell-prompt-compose&lt;/code&gt;, which automatically grabs the active region. This led me to explore a few options to select a region, or maybe even roll my own. I should also mention, these regions don't typically require compilable/complete structures.&lt;/p&gt;
&lt;p&gt;In most of these instances, I just reach out to one of my region favourites like &lt;a href="https://github.com/magnars/expand-region.el"&gt;expand-region&lt;/a&gt;, &lt;code&gt;mark-defun&lt;/code&gt;, or &lt;code&gt;mark-whole-buffer&lt;/code&gt;. Alternatively, I navigate to different points using sexp commands like &lt;code&gt;backward-sexp&lt;/code&gt; and &lt;code&gt;forward-sexp&lt;/code&gt; (or maybe something like &lt;code&gt;sp-backward-up-sexp&lt;/code&gt; from &lt;a href="https://github.com/Fuco1/smartparens"&gt;smartparens&lt;/a&gt;), using &lt;code&gt;set-mark-command&lt;/code&gt; in-between to activate the region.&lt;/p&gt;
&lt;p&gt;While these commands typically yield balanced expressions, it's often unnecessary for my LLM queries. This led me to &lt;a href="https://indieweb.social/@xenodium/113158018599745252"&gt;ask folks&lt;/a&gt; for different ways of selecting regions, which highlighted great package suggestions like &lt;a href="https://github.com/abo-abo/avy"&gt;avy&lt;/a&gt;, &lt;a href="https://github.com/meow-edit"&gt;meow&lt;/a&gt;, and &lt;a href="https://github.com/leoliu/easy-kill"&gt;easy-kill&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While I've been intrigued by &lt;code&gt;meow&lt;/code&gt;'s modal editing for some time, I'm not ready for that fair trial jump. Will have to postpone it for a little longer.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Easy-kill&lt;/code&gt; offers &lt;code&gt;easy-mark&lt;/code&gt;, in some ways similar to the built-in &lt;code&gt;mark-sexp&lt;/code&gt;, but with additional marking heuristics and possibly other goodies I missed. At present, I get similar benefits from the likes of &lt;code&gt;expand-region&lt;/code&gt; and the other sexp helpers.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Avy&lt;/code&gt;'s &lt;code&gt;avy-kill-ring-save-region&lt;/code&gt; could work for my purpose, though I wish it left the region active. Maybe that's already possible? I could look into extending &lt;code&gt;avy&lt;/code&gt;, though &lt;a href="https://mastodon.social/@ctietze/113154167264208507"&gt;Christian's suggestions&lt;/a&gt; led me to lean more on visual feedback in my own region-expanding experiments.&lt;/p&gt;
&lt;p&gt;The goal was to enable extending regions in both vertical directions by simultaneously adding lines at both ends. Sure, this doesn't guarantee structural completeness, but it may just be enough for my LLM-feeding purpose. Maybe this already exists in the Emacs universe, but hey, it's an excuse to throw some elisp lines together…&lt;/p&gt;
&lt;p&gt;Assuming there's an existing active region, expanding in both directions is pretty straightforward.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun bubble-expand()
  &amp;quot;Expand region.&amp;quot;
  (interactive)
  (when (&amp;gt; (point) (mark))
    (exchange-point-and-mark))
  (forward-line -1)
  (exchange-point-and-mark)
  (forward-line 1)
  (exchange-point-and-mark))

(defun bubble-shrink ()
  &amp;quot;Shrink region.&amp;quot;
  (interactive)
  (when (&amp;lt; (point) (mark))
    (exchange-point-and-mark))
  (forward-line -1)
  (exchange-point-and-mark)
  (forward-line 1))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While I've yet to use this region-expanding approach long enough to validate its usefulness, it sure is fun to play with it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/emacs-bubble-mode/bubble-expand-shrink.gif" /&gt;&lt;/p&gt;
&lt;p&gt;This got me thinking, what other funky things I could do with the region? Could I shift the region selection like a viewport of sorts? As you now expect, the answer in Emacs is almost always of course we can…&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun bubble-shift-up ()
  &amp;quot;Shift the region up by one line.&amp;quot;
  (interactive)
  (when (&amp;gt; (point) (mark))
    (exchange-point-and-mark))
  (forward-line -1)
  (forward-line 0)
  (exchange-point-and-mark)
  (forward-line -1)
  (end-of-line)
  (activate-mark)
  (exchange-point-and-mark))

(defun bubble-shift-down ()
  &amp;quot;Shift the region down by one line.&amp;quot;
  (interactive)
  (when (&amp;gt; (point) (mark))
    (exchange-point-and-mark))
  (forward-line)
  (forward-line 0)
  (exchange-point-and-mark)
  (forward-line)
  (end-of-line)
  (activate-mark)
  (exchange-point-and-mark))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/emacs-bubble-mode/bubble-shift.gif" /&gt;&lt;/p&gt;
&lt;p&gt;My friend Vaarnan also suggested looking into UX around providing line count, which is possible by providing a prefix into &lt;code&gt;bubble-expand-region&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;C-5 M-x bubble-expand-region
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/emacs-bubble-mode/bubble-number.gif" /&gt;&lt;/p&gt;
&lt;p&gt;These commands alone aren't as effective unless we have some key-bindings around them. I've tied things up into a minor mode, called… you guessed it: &lt;code&gt;bubble-mode&lt;/code&gt;. Oooh, a mode, you may say it's now official ;) Well, no. It's still an experiment of sorts and currently lives in &lt;a href="https://github.com/xenodium/dotsies/blob/main/emacs/ar/bubble.el"&gt;my Emacs config repo&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The key bindings I've chosen are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C-c C-w&lt;/code&gt;: Enter bubble-mode.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-p&lt;/code&gt;: bubble-expand.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C-n&lt;/code&gt;: bubble-shrink.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;S-C-p&lt;/code&gt;: bubble-move-up.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;S-C-n&lt;/code&gt;: bubble-move-down.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Numbers 1-0&lt;/code&gt;: Expand 1 to 10 lines.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;RET&lt;/code&gt;: Exit bubble-mode.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note: Inspired by expand-region, any other key binding/command automatically exits &lt;code&gt;bubble-mode&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;C-c C-w&lt;/code&gt; kinda works for me as &lt;code&gt;C-c w&lt;/code&gt; is already bound to &lt;code&gt;expand-region&lt;/code&gt;. Let's see if that sticks, though I may have to give up the &lt;code&gt;org-refile&lt;/code&gt; binding.&lt;/p&gt;
&lt;p&gt;So does it work for my original LLM intent? We shall see, but it seems to so far. You can play with it if you'd like (it's on &lt;a href="https://github.com/xenodium/dotsies/blob/main/emacs/ar/bubble.el"&gt;github&lt;/a&gt;). Here's what that flow now looks like:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/emacs-bubble-mode/bubble-whats-wrong.gif" /&gt;&lt;/p&gt;
&lt;h2&gt;Enjoying this content? Using one of my Emacs packages?&lt;/h2&gt;
&lt;p&gt;Help make the work sustainable. Consider &lt;a href="https://github.com/sponsors/xenodium"&gt;sponsoring&lt;/a&gt;. I'm also building &lt;a href="https://lmno.lol/"&gt;lmno.lol&lt;/a&gt;. A platform to &lt;a href="https://indieweb.social/@xenodium/112265481282475542"&gt;drag and drop&lt;/a&gt; your blog to the web.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Thu, 19 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/emacs-bubble-mode</guid></item><item><title>Nothing said “home” to me more than an empty house</title><link>https://bowendwelle.substack.com/p/nothing-says-home-to-me-more-than</link><description>Home is about walking what&amp;#8217;s real&amp;#8212;and not away&amp;#8212;or alone.</description><author>An Ordinary Disaster</author><pubDate>Wed, 18 Sep 2024 19:30:43 GMT</pubDate><guid isPermaLink="true">https://bowendwelle.substack.com/p/nothing-says-home-to-me-more-than</guid></item><item><title>Running with my dog</title><link>https://honza.pokorny.ca/2024/09/running-with-my-dog/</link><description>&lt;p&gt;A few times a week I go running with my dog on a local trail.  I wake up early and immediately hydrate.  Then I put on my running clothes and go downstairs.  I fill up my water bottles and put them in the running vest.  I grab a treat for the dog.  I strap the heart rate monitor to my chest.  I lace up my shoes.  I select a workout on my watch.  I put on a buff and sometimes a headlamp.  I put my phone in my vest.  Then I grab the leash and we can go.&lt;/p&gt;
&lt;p&gt;And what does my running companion do to get ready?  Nothing.  Absolutely &lt;em&gt;nothing&lt;/em&gt;.  He just gets up and runs out naked.&lt;/p&gt;</description><author>Honza Pokorný</author><pubDate>Wed, 18 Sep 2024 18:00:00 GMT</pubDate><guid isPermaLink="true">https://honza.pokorny.ca/2024/09/running-with-my-dog/</guid></item><item><title>On this day, September 18</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-18/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2012 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Wed, 18 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-18/</guid></item><item><title>The art of organizing code</title><link>https://gourav.io/blog/organize-code</link><description>How to organize backend and frontend code for better readability and easier maintenance.</description><author>Gourav Goyal</author><pubDate>Wed, 18 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gourav.io/blog/organize-code</guid></item><item><title>On Poetry</title><link>https://zserge.com/posts/poetry/</link><description>Recently with friends we talked about an early web site I&amp;rsquo;ve programmed, probably the very first web service I&amp;rsquo;ve ever done. It was a simple dictionary.
Back then, we had Nokia phones without real web browsers, just something called the WAP protocol and WML markup. Since I struggled with foreign languages as a student (I still do!), I decided to create a mobile site in WAP/WML to help me translate words, show definitions, thesaurus entries, pronunciations, and more.</description><author>zserge's blog</author><pubDate>Wed, 18 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zserge.com/posts/poetry/</guid></item><item><title>Seizing the Means of Re-Production</title><link>https://taylor.town/oh-infringement</link><description>What's the deal with Monsanto?</description><author>taylor.town</author><pubDate>Wed, 18 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/oh-infringement</guid></item><item><title>Offensive Horticulture</title><link>https://taylor.town/oh</link><description>Plant production puts plant-propagators in potential penal peril.</description><author>taylor.town</author><pubDate>Wed, 18 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/oh</guid></item><item><title>The lost taglines</title><link>https://paperless.blog/the-lost-taglines</link><description>Software documentation usually does not include the most important facts you want to know as a beginner. The kind of stuff you might internalise after using something for years, but you could not have learned by reading the documentation. The stuff which should show up as a blinking orange &amp;amp; teal banner on top of the documentation page — the lost taglines:</description><author>Paperless</author><pubDate>Wed, 18 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://paperless.blog/the-lost-taglines</guid></item><item><title>Diffusion Models Are Real-Time Game Engines</title><link>https://www.hlfshell.ai/posts/gamengen/</link><description>Your browser does not support the video tag.  Google Deepmind recently released Diffusion Models Are Real-Time Game Engines [Site] | [Paper], a fascinating paper wherein a modified Stable Diffusion model acts as the game engine for the classic game of DOOM. Player actions are fed directly into the model (which they called GameNGen), which outputs a generated image of the next frame 20 times a second. The game state and display is entirely handled by the model itself.</description><author>hlfshell</author><pubDate>Wed, 18 Sep 2024 00:25:00 GMT</pubDate><guid isPermaLink="true">https://www.hlfshell.ai/posts/gamengen/</guid></item><item><title>On this day, September 17</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-17/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2012 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Tue, 17 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-17/</guid></item><item><title>Tiny Great Languages: PL/0</title><link>https://zserge.com/posts/langs-pl0/</link><description>This is part 6 from series &amp;ldquo;Tiny Great Languages&amp;rdquo;.
Final code is on Github. Part 1: Assembly. Part 2: BASIC. Part 3: Forth/MOUSE. Part 4: Lisp. Part 5: APL/K. Part 6: PL/0. Let&amp;rsquo;s talk about Pascal. It&amp;rsquo;s the language I first learned many years ago, and it becomes the final one we cover in this series. Well, not the full version of Pascal, but a tiny educational subset defined by the legendary Niklaus Wirth.</description><author>zserge's blog</author><pubDate>Tue, 17 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zserge.com/posts/langs-pl0/</guid></item><item><title>Town Hall #25: Simularium</title><link>https://taylor.town/town-hall-0025</link><description>a man, a plan, a canal, Japan</description><author>taylor.town</author><pubDate>Tue, 17 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/town-hall-0025</guid></item><item><title>A new breed of biotech</title><link>https://atelfo.github.io/2024/09/17/a-new-breed-of-biotech.html</link><description/><author>Alex’s blog</author><pubDate>Tue, 17 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://atelfo.github.io/2024/09/17/a-new-breed-of-biotech.html</guid></item><item><title>Noise</title><link>/posts/rn-noise/</link><description>&lt;p&gt;&lt;em&gt;I enjoyed learning about the concept. However if you plan to read that book, I would advise to start with the last chapter, which gives 80% of the concept, then read the rest as a deep-dive in the details. It would warrant a 4/5, but the unnecessary length, and the reversed structure reduces it by .25. AND YES, I’m conscious this is a noisy rating, and I’m fine with it. Take that!&lt;/em&gt;&lt;/p&gt;
&lt;h1 id="outline"&gt;Outline&lt;/h1&gt;</description><author>Charles Féval</author><pubDate>Tue, 17 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">/posts/rn-noise/</guid></item><item><title>Introduction to LLM</title><link>https://blog.adnansiddiqi.me/introduction-to-llm/</link><description>&lt;p&gt;So, I am starting this  GenAI series where I will share insights related to GenAI and large language models (LLMs). This is the first post, where I’ll discuss what LLMs are, how they work, and other related topics. What are Large Language Models Large Language Models, or LLMs, are a type of AI that can understand and generate human-like text. They’re the technology behind smart chatbots and writing tools, making our interactions with machines feel more natural. LLMs are built on foundation models. These foundation models are large AI models pre-trained on vast amounts of unlabeled and self-supervised data to perform a wide range of tasks, serving as the base for specialized applications. This means the model learns from patterns in the data, allowing it to generate generalizable and adaptable outputs. LLMs are a specific type of foundation model, designed for text and text-related tasks. They are trained on massive amounts of text, such as books, articles, and conversations. When I say &amp;#8220;large,&amp;#8221; I’m referring to models trained on data measured in petabytes, and their size can range in the tens of gigabytes. LLMs are also large in terms of parameter count—parameters being values the model adjusts as it learns. The more parameters, the more complex the model. For instance, GPT-3 has 175 billion parameters, GPT-4 around 1 trillion, LLaMA has 13 billion, and Anthropic’s Claude 1 has 52 billion parameters. How LLMs work LLMs are based on three major components: Data, Architecture, and Training. The data part is already covered above. The architecture is based on a neural network, and for GPT, that is a transformer. Transformers are a type of AI model designed to process and understand data, especially text, in a highly efficient way. They use a mechanism called attention to focus on important parts of the input, allowing them to handle long text sequences and capture complex relationships between words. During training, the model learns to predict the next word in a sentence. For instance, “Dog is a bird,” after multiple iterations and parameter adjustments, it matches the desired output: “Dog is an animal.” Fine-tuned models A fine-tuned model is a specific LLM that is trained on a specific kind of data to serve a niche. For instance, a medLLM is an LLM trained on all the data related to the medical field, hence giving more specific information to that field instead of giving wrong information or hallucinating. What is Hullicinatation In the context of LLMs, hallucination refers to instances when the model generates information that is incorrect, misleading, or completely made up, even though it sounds plausible. These hallucinations can occur because the model doesn’t actually know facts but instead predicts likely text based on patterns from its training data, sometimes producing false or non-existent information. Besides fine-tuning, you can reduce it by providing better input, aka a prompt. Large Language Model (LLM) Applications Customer Support:  You can create AI-based smart chat/voice bots that can replace human customer representatives and handle frequent queries. You can train an LLM using your organization’s data, FAQs, and other relevant resources to help respond effectively. For example, in the company I work for, I developed an AI-based chatbot that leverages OpenAI APIs to respond to customers. Ticket-related information is fetched via our internal API in JSON format, which is then provided to an OpenAI Assistant. The assistant responds to customers based on the ticket data in JSON format, PDFs from our knowledge base, and our custom prompt that sets a specific tone and persona. Content Creation:  You can use LLMs to create articles, emails, video scripts, and a lot more. Translation:  You can use LLMs to translate text from one language to another. Software Development:  You can use LLMs to generate code or even fix existing code. LLMs can write tests for you and do a lot more, even converting code snippets from one language to another. Sentiment Analysis: LLMs are good for sentiment analysis. It doesn’t take much time to come up with something like this. Conclusion In this post, we discussed what LLMs are, how they work, and how they can be beneficial in different use cases. In the coming posts, we will discuss further how you, as a programmer, can leverage your existing coding skills to make apps and earn money. Stay tuned! If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/introduction-to-llm/"&gt;Introduction to LLM&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Mon, 16 Sep 2024 22:00:30 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/introduction-to-llm/</guid></item><item><title>Practical Deep Learning, Lesson 1, Image Models</title><link>https://www.danielcorin.com/til/fastai/lesson1-image-models/</link><description>Practical Deep Learning, Lesson 1, Image Models</description><author>Thought Eddies</author><pubDate>Mon, 16 Sep 2024 20:28:56 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/fastai/lesson1-image-models/</guid></item><item><title>Three Degrees of Freedom</title><link>https://superbowl.substack.com/p/three-degrees-of-freedom</link><description>We don't get to choose reality, but at least we get to choose our view of it.</description><author>Superb Owl</author><pubDate>Mon, 16 Sep 2024 19:45:21 GMT</pubDate><guid isPermaLink="true">https://superbowl.substack.com/p/three-degrees-of-freedom</guid></item><item><title>A Family Affair</title><link>https://olshansky.info/movie/family_affair/</link><description>Olshansky's review of A Family Affair</description><author>🦉 olshansky 🦁</author><pubDate>Mon, 16 Sep 2024 18:40:33 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/movie/family_affair/</guid></item><item><title>Practicing with Polylines Part 2 - Get Your Data (as a polyline) From Strava</title><link>https://josh.works/polyline-practice-again-strava-auth</link><description>&lt;p&gt;Last time, I did a &lt;a href="/polyline-practice-again"&gt;minimum first pass&lt;/a&gt; on rendering a polyline on a map.&lt;/p&gt;

&lt;p&gt;It wasn’t just any polyline, though, it was a path of a walk I went on. (Technically, just a fragment of a path).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;this is a heavy draft, I’ve had issues getting this all working well in the past, still have to suss it today.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;the dictionary definition of a polyline is ‘some string that decodes into lat/long pairs that can be traced on a map’. I’m interested in the lines I’ve always looked at, which were made by Strava, from a device on my person, while I was walking, or biking, or riding my scooter.&lt;/p&gt;

&lt;p&gt;So it’s data, but it’s also extremely-specific-to-me location data, and it obviously has the capacity to be fascinating.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;My&lt;/em&gt; data is likely to be boring to you, though.&lt;/p&gt;

&lt;p&gt;What might not be boring would be &lt;em&gt;your&lt;/em&gt; data.&lt;/p&gt;

&lt;p&gt;You could go on a walk right now, with the &lt;a href="https://www.strava.com/"&gt;Strava app&lt;/a&gt; running on your phone, save the activity, and a moment later be looking at a map with that new activity data rendered upon it.&lt;/p&gt;

&lt;p&gt;Lets do just that. Like any good thing on the internet, there’s others who have done this thing in a concise and better-than-i-could way.&lt;/p&gt;

&lt;p&gt;These were my first sources and inspiration for this project:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://www.markhneedham.com/blog/2017/04/29/leaflet-strava-polylines-osm/"&gt;https://www.markhneedham.com/blog/2017/04/29/leaflet-strava-polylines-osm/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://gist.github.com/mneedham/34b923beb7fd72f8fe6ee433c2b27d73"&gt;https://gist.github.com/mneedham/34b923beb7fd72f8fe6ee433c2b27d73&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today, I’m going to &lt;em&gt;try&lt;/em&gt; to ‘quickly’ get a working auth ‘thing’ set up, close-enough to a copy/paste ruby script, so you can run a script or run some ruby commands in an IRB terminal, and get your data back from Strava, including any activity data polyline strava has.&lt;/p&gt;

&lt;p&gt;The goal being a polyline you can copy and paste, yourself, into a html document and get a cool map, showing off a walk or journey you went on yourself. It’s &lt;em&gt;strongly&lt;/em&gt; based on Mark Needham’s stuff.&lt;/p&gt;

&lt;p&gt;First, download the &lt;a href="https://www.strava.com"&gt;Strava&lt;/a&gt; app (android/iphone whatever: https://www.strava.com/)&lt;/p&gt;

&lt;p&gt;Create an account, and then go on a ten minute walk while tracking that as an activity in the strava app. Finish the walk, end the activity. It’ll upload to Strava, and now we can use the Strava API to get that activity data back out and look at.&lt;/p&gt;

&lt;p&gt;You can keep working through this guide without activity on your Strava account, so maybe plan on taking a ten minute walk in the next hour or so.&lt;/p&gt;

&lt;h2 id="set-up-a-strava-application"&gt;Set up a ‘strava application’&lt;/h2&gt;

&lt;p&gt;Strava has apps, and you can give those apps permissions at a per-app basis. You’ll set up an app that you’ll then give permission to know certain things about your data.&lt;/p&gt;

&lt;p&gt;So, to make the app account, and get your account id/ key, head to the developer settings. go to &lt;a href="https://www.strava.com/settings/api"&gt;https://www.strava.com/settings/api&lt;/a&gt; and follow the prompts to get an API application set up.&lt;/p&gt;

&lt;p&gt;When you have your &lt;code class="language-plaintext highlighter-rouge"&gt;client_id&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;client_secret&lt;/code&gt; available, you’re ready to continue.&lt;/p&gt;

&lt;p&gt;We might use &lt;a href="https://github.com/dblock/strava-ruby-client"&gt;https://github.com/dblock/strava-ruby-client&lt;/a&gt; at some point.&lt;/p&gt;

&lt;h2 id="authorize-the-app-to-access-your-strava-data"&gt;Authorize the app to access your strava data&lt;/h2&gt;

&lt;p&gt;You’re going to need to generate a token (a refresh token and )&lt;/p&gt;

&lt;p&gt;We’re going to do some creative things. Paste this into a pry session.&lt;/p&gt;

&lt;p&gt;do &lt;code class="language-plaintext highlighter-rouge"&gt;gem install 'strava-ruby-client'&lt;/code&gt; first.&lt;/p&gt;

&lt;p&gt;Then, fire up a pry session or irb session in your terminal. I recommend a text file where you can keep text for copy/paste accessibility. Copy the below text into your own blank file, update the client_id and client_secret variables (don’t commit any of this to github, you can make it an environment variable later. Or now.)&lt;/p&gt;

&lt;div class="language-ruby highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'strava-ruby-client'&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Strava&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;OAuth&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;client_id: &lt;/span&gt;&lt;span class="s2"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;client_secret: &lt;/span&gt;&lt;span class="s2"&gt;"secret"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;redirect_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorize_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;redirect_uri: &lt;/span&gt;&lt;span class="s1"&gt;'https://localhost:4000/oauth'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;approval_prompt: &lt;/span&gt;&lt;span class="s1"&gt;'force'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;response_type: &lt;/span&gt;&lt;span class="s1"&gt;'code'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;scope: &lt;/span&gt;&lt;span class="s1"&gt;'activity:read_all'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;state: &lt;/span&gt;&lt;span class="s1"&gt;'magic'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;this did not work:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;https://www.strava.com/oauth/authorize?approval_prompt=force&amp;amp;client_id=63764&amp;amp;redirect_uri=developers.strava.com&amp;amp;response_type=code&amp;amp;scope=activity%3Aread_all&amp;amp;state=magic&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;this worked:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;https://www.strava.com/oauth/authorize?client_id=my_client_id&amp;amp;response_type=code&amp;amp;redirect_uri=http://localhost/exchange_token&amp;amp;approval_prompt=force&amp;amp;scope=activity:read_all&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Look in the URL for “code” variable, and carry it on to the next step, where we give Strava this code, it’s treated as a ‘refresh token’, and if we give strava a refresh token it’ll give us back a valid access token that can then be included in the request authorization of every subsequent API call, and we’ll get back data for the strava account identified by that access token. This is all ‘just’ ‘basic’ auth stuff, but it can get tricky sometimes.&lt;/p&gt;

&lt;div class="language-ruby highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"uri"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/http"&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://www.strava.com/oauth/token?client_id=YOURCLIENTID&amp;amp;client_secret=CLIENT_SECRET&amp;amp;refresh_token=REFRESH_TOKEN&amp;amp;grant_type=refresh_token"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;https&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_ssl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_body&lt;/span&gt;

&lt;span class="c1"&gt;# look at the response before continuing, save the `access_token`&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In following that link, and approving the app, you’ve given your own app access to your Strava account data. Finish the oauth “flow” to view your data.&lt;/p&gt;

&lt;p&gt;With that code, in Postman you can now make a request.&lt;/p&gt;

&lt;p&gt;To see if it works, you can also paste this into an IRB session:&lt;/p&gt;

&lt;div class="language-ruby highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"uri"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/http"&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://www.strava.com/api/v3/activities/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;https&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_ssl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Bearer ACCESS_TOKEN_FROM_PRIOR_STEP"&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_body&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;boom. Look at your activities! The polyline(s) might be visible now. If so, phenominal! Save them to a text file, or a CSV, manually or automatically.&lt;/p&gt;

&lt;p&gt;To get the detailed polyline, and not just the summary polyline, you need one more request:&lt;/p&gt;

&lt;div class="language-ruby highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"uri"&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s2"&gt;"net/http"&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;URI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"https://www.strava.com/api/v3/activities/YOUR_ACTIVITY_ID?include_all_efforts=true"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;https&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use_ssl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;

&lt;span class="n"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Net&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"••••••"&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;https&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_body&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Does that work? I hope it does for you. It worked for me.&lt;/p&gt;</description><author>Josh Thompson</author><pubDate>Mon, 16 Sep 2024 16:00:00 GMT</pubDate><guid isPermaLink="true">https://josh.works/polyline-practice-again-strava-auth</guid></item><item><title>iStick Pico 25 OLED Replacement</title><link>https://www.xythobuz.de/2024_09_16_istick_pico_oled.html</link><description>&lt;p&gt;Years ago I bought a cheap chinese E-Cigarette 510 box mod, the Eleaf iStick Pico 25.
It's running the ArcticFox custom firmware which sadly seems to be &lt;a href="https://nfeteam.org/"&gt;no longer available&lt;/a&gt;.
I never really used it for anything, but for &lt;a href="https://vapeengineering.de/"&gt;reasons&lt;/a&gt; I want to get it going again.
Unfortunately the display is so dim that it can no longer be used.
So I wanted to repair the device.&lt;/p&gt;
&lt;p&gt;I found &lt;a href="https://old.reddit.com/r/Vaping/comments/4t5w0k/the_screen_of_my_eleaf_pico_is_fading_every_day/jz009z6/"&gt;this comment on Reddit&lt;/a&gt; explaining that it's just a standard SSD1306 128x32 0.91" OLED display.
&lt;a href="https://old.reddit.com/r/Vaping/comments/18d9kkq/brand_new_istick_pico_75w_with_a_dim_screen/krzd9ix/"&gt;This guy&lt;/a&gt; says initially Eleaf used ribbon cable connectors, making the replacement easy, and one shouldn't try without them.
But actually soldering on a replacement is very easy.&lt;/p&gt;
&lt;p&gt;First open the device and carefully remove the plastic display mount and desolder the ribbon cable.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/istick_pico_oled_replacement_1.jpg"&gt;&lt;img alt="Old display, top view" class="pic" src="https://www.xythobuz.de/img/istick_pico_oled_replacement_1_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/istick_pico_oled_replacement_2.jpg"&gt;&lt;img alt="Display holder removed" class="pic" src="https://www.xythobuz.de/img/istick_pico_oled_replacement_2_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The most difficult part for me was getting the &lt;a href="https://www.az-delivery.de/en/products/0-91-zoll-i2c-oled-display"&gt;replacement OLED&lt;/a&gt; removed from the breakout board.
I used some lighter fuel to dissolve the glue and carefully levered and cut it off, trying not to break the glass or destroy the ribbon PCB.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/istick_pico_oled_replacement_3.jpg"&gt;&lt;img alt="Replacement display" class="pic" src="https://www.xythobuz.de/img/istick_pico_oled_replacement_3_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Then just solder it on like any other SMD part.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/istick_pico_oled_replacement_4.jpg"&gt;&lt;img alt="Repair done" class="pic" src="https://www.xythobuz.de/img/istick_pico_oled_replacement_4_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Works great!&lt;/p&gt;</description><author>xythobuz.de Blog</author><pubDate>Mon, 16 Sep 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://www.xythobuz.de/2024_09_16_istick_pico_oled.html</guid></item><item><title>LARS v2</title><link>https://www.xythobuz.de/lars_v2.html</link><description>&lt;p&gt;So you may have read about our project &lt;a href="lars.html"&gt;LARS&lt;/a&gt;.
As mentioned I didn't have any hardware in the end to finish firmware development.
So I slightly updated the circuitry, layed out a new board, had it manufactured in China and assembled at home.&lt;/p&gt;
&lt;p&gt;This is LARS v2, the second iteration of the same idea.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_1.jpg"&gt;&lt;img alt="LARS v2" class="pic" src="https://www.xythobuz.de/img/lars_v2_1_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars2_midi.mp4"&gt;&lt;img alt="LARS v2 playing MIDI example file" class="pic" src="https://www.xythobuz.de/img/lars2_midi_thumb.png" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;It's basically the same device, just with five additional switches.
This allows for some more freedom in the user interface design.
Now the loop mode can mute individual tracks.
There's also USB MIDI support.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars2_parts.jpg"&gt;&lt;img alt="LARS v2 PCBs and parts" class="pic" src="https://www.xythobuz.de/img/lars2_parts_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_loop_controls.jpg"&gt;&lt;img alt="LARS Loop Station controls" class="pic" src="https://www.xythobuz.de/img/lars_loop_controls_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;As &lt;a href="2024_05_05_auto_project_docs.html"&gt;usual&lt;/a&gt; I've added auto-generated &lt;a href="https://xythobuz.github.io/lars/"&gt;documentation&lt;/a&gt;, including &lt;a href="https://xythobuz.github.io/lars/pcb2_pcb.html"&gt;interactive 3D renders of the PCB&lt;/a&gt; and some &lt;a href="https://xythobuz.github.io/lars/usage.html"&gt;usage hints&lt;/a&gt;.
The &lt;a href="https://xythobuz.github.io/lars/plot/fab_pcb2.zip"&gt;gerber files&lt;/a&gt; and &lt;a href="https://xythobuz.github.io/lars/pcb2.html"&gt;BOM&lt;/a&gt; to order your own are also available.&lt;/p&gt;
&lt;p&gt;Unfortunately I'm kind of in the same situation now as with LARS v1.
I've gone much further with the firmware, it's now mostly usable.
But I've also given the hardware away again, so I can't finish it.&lt;/p&gt;
&lt;p&gt;&lt;s&gt;I should assemble another one soon...&lt;/s&gt;&lt;/p&gt;
&lt;h2&gt;Assembly Guide&lt;/h2&gt;
&lt;p&gt;You want to build your own LARS v2 and have all the parts ready to go?
Then let's build it!&lt;/p&gt;
&lt;p&gt;In general you want to go from SMD / SMT (surface mount) parts to THT (through-hole), going from large to small for the SMD parts, and small to large for the THT parts.&lt;/p&gt;
&lt;p&gt;First place the SMD Raspberry Pi Pico module so all pads are properly aligned.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_1.jpg"&gt;&lt;img alt="Empty PCB" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_1_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_2.jpg"&gt;&lt;img alt="Pico placed" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_2_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now first solder on one corner, then go through both rows.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_3.jpg"&gt;&lt;img alt="First Pico corner soldered" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_3_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_4.jpg"&gt;&lt;img alt="One Pico row soldered" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_4_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_5.jpg"&gt;&lt;img alt="Pico soldering finished" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_5_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The battery holder snaps in with two plastic clips.
Soldering the pads can be a bit tricky.
Even though there is a hole for the solder to flow through to the pad, you can not see it well.
Try to apply a bit of pressure to get the pads to connect reliably.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_6.jpg"&gt;&lt;img alt="Battery holder snapped in" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_6_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_7.jpg"&gt;&lt;img alt="Battery holder soldered on" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_7_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Now place all the pushbuttons and solder them in.
Their legs have a snap fit so they will hold on properly.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_8.jpg"&gt;&lt;img alt="Pushbuttons placed" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_8_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_9.jpg"&gt;&lt;img alt="Buttons soldered" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_9_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Next the resistors.
I'm bending their legs slightly so they stay in place when turning the board over.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_10.jpg"&gt;&lt;img alt="LED resistors placed" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_10_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_11.jpg"&gt;&lt;img alt="LED resistor legs bent" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_11_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_12.jpg"&gt;&lt;img alt="LED resistors soldered in" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_12_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_13.jpg"&gt;&lt;img alt="Voltage divider resistors done" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_13_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The diodes and LEDs can be placed next, with the same leg-bending trick as before.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_14.jpg"&gt;&lt;img alt="Diodes in place" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_14_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_15.jpg"&gt;&lt;img alt="LEDs placed" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_15_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The encoder snaps in as well.
For the switch and OLED, make sure to apply a bit of pressure when turning the board over, so they are firmly in place.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_16.jpg"&gt;&lt;img alt="Switch, encoder and OLED placed" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_16_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_17.jpg"&gt;&lt;img alt="Switch, encoder and OLED soldered" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_17_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The pins of the MOSFET module screw-terminals fit into the board holes and can be soldered on directly.
For the small logic pins use a bit of the cut-off legs from the LEDs or resistors.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_18.jpg"&gt;&lt;img alt="MOSFETs in place, top" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_18_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_19.jpg"&gt;&lt;img alt="MOSFETS in place, bottom" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_19_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_20.jpg"&gt;&lt;img alt="MOSFET terminals soldered" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_20_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_21.jpg"&gt;&lt;img alt="MOSFET logic pins soldered" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_21_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;For the regulator I'm using standard pins like they probably came with your Pico.
But you could also use some more of the cut-off legs from the previous steps.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_22.jpg"&gt;&lt;img alt="Regulator pins prepared" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_22_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_23.jpg"&gt;&lt;img alt="Regulator in place" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_23_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_24.jpg"&gt;&lt;img alt="Regulators soldered, top" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_24_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_25.jpg"&gt;&lt;img alt="Regulators soldered, bottom" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_25_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;The charger pad does not quite fit the connections of the module.
So I'm using two pins per connection in a 90 degree angle.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_26.jpg"&gt;&lt;img alt="Charger pins prepared" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_26_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_27.jpg"&gt;&lt;img alt="Charger module prepared" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_27_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;And that's it.&lt;/p&gt;
&lt;div class="lightgallery_new"&gt;
&lt;div class="border"&gt;&lt;a href="https://www.xythobuz.de/img/lars_v2_assembly_28.jpg"&gt;&lt;img alt="Finished board" class="pic" src="https://www.xythobuz.de/img/lars_v2_assembly_28_small.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;

&lt;p&gt;Good luck and have fun!&lt;/p&gt;</description><author>xythobuz.de Blog</author><pubDate>Mon, 16 Sep 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://www.xythobuz.de/lars_v2.html</guid></item><item><title>On this day, September 16</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-16/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2006 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Mon, 16 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-16/</guid></item><item><title>5x Faster Set Intersections: SVE2, AVX-512, &amp;amp; NEON 🤐</title><link>https://ashvardanian.com/posts/simd-set-intersections-sve2-avx512/</link><description>Achieving 5x faster set intersections on AWS Graviton 4 using Arm's SVE2 and Intel's AVX-512 with specialized instructions like HISTCNT, MATCH, and VP2INTERSECT.</description><author>Ash's Blog</author><pubDate>Mon, 16 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/simd-set-intersections-sve2-avx512/</guid></item><item><title>Personnel update</title><link>https://ntietz.com/blog/personnel-update/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;This is inspired by receiving a "personnel update" when a friend was fired many years ago. It felt coldly impersonal for such a deeply personal event, so I imagined what it would be like if the same approach were taken to &lt;em&gt;other&lt;/em&gt; deeply personal events.&lt;/p&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Subject: Personnel Update&lt;/strong&gt; &lt;br /&gt;
&lt;strong&gt;From: dad@family.com&lt;/strong&gt; &lt;br /&gt;
&lt;strong&gt;To: son@family.com&lt;/strong&gt; &lt;br /&gt;
&lt;strong&gt;CC: lawyer@lawfirm.com&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Dear son,&lt;/p&gt;
&lt;p&gt;As of 1:00 PM EST this afternoon, we decided to part ways with your mom. Your mom has been with our family for fourteen years. Unfortunately, over the last few months, there have been escalating problems with communication and with performance. She has failed to meet stated metrics.&lt;/p&gt;
&lt;p&gt;In our family, we are committed to providing an environment where everyone can thrive. Your mother failed to meet those standards, and we have decided that it would be best for her to seek opportunities that are a better fit. We have thanked her for her contributions and wish her well.&lt;/p&gt;
&lt;p&gt;If you have any questions or concerns, please reach out to our lawyer or to me directly.&lt;/p&gt;
&lt;p&gt;Regards,
Dad&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div style="text-align: center;"&gt;* * *&lt;/div&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Subject: Team Update&lt;/strong&gt; &lt;br /&gt;
&lt;strong&gt;From: mom@family.com&lt;/strong&gt; &lt;br /&gt;
&lt;strong&gt;To: daughter@family.com&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Dear daughter,&lt;/p&gt;
&lt;p&gt;As of 3:00 PM EST this afternoon, we have decided to part ways with Fido. Fido has been a loyal companion with our family for 14 months. Unfortunately, over the last three months, there have been escalating problems with behavior. There have been multiple incidents of Fido damaging the family's personal property while your father and I are at work. Further, the neighbors have complained of loud daytime barking and whining on multiple occasions.&lt;/p&gt;
&lt;p&gt;In our family, we are committed to providing an environment with respect for each other and personal property. We have been working with Fido to improve these issues, but he has continued to whine during the daytime when we leave and chew up our furniture. We have decided that he is not a fit for our family. We thank him for his contributions, and wish him well in his future endeavors.&lt;/p&gt;
&lt;p&gt;If you have any questions or concerns, please reach out to me or your father directly.&lt;/p&gt;
&lt;p&gt;Regards,
Mom&lt;/p&gt;
&lt;/blockquote&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 16 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/personnel-update/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Finding Private Information Through Resumes on Google Search</title><link>https://nelson.cloud/finding-private-information-through-resumes-on-google-search/?ref=rss</link><description>Reconsider uploading your resume on the open web.</description><author>Nelson Figueroa</author><pubDate>Mon, 16 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/finding-private-information-through-resumes-on-google-search/?ref=rss</guid></item><item><title>The Cloud Hasn't Been Won</title><link>https://olivergilan.com/cloud-hasnt-been-won/</link><description>Cloud hosting is one of the biggest markets in the world and it's right there for the taking.</description><author>Oliver Gilan</author><pubDate>Mon, 16 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://olivergilan.com/cloud-hasnt-been-won/</guid></item><item><title>Nitpicky Notes Part 1</title><link>https://cmart.blog/nitpicky-notes/</link><description>&lt;p&gt;A friend showed me &lt;a href="https://www.youtube.com/watch?app=desktop&amp;amp;v=XRpHIa-2XCE"&gt;Simple, Non-Commercial, Open Source Notes&lt;/a&gt;. (Warning, this video will berate you for 30 minutes.) My values are different from the author&amp;rsquo;s, but I appreciate their methodical(??) approach to articulate their needs and explore the solution space of digital note-taking systems. Here, I do some of the same.&lt;/p&gt;
&lt;p&gt;&lt;img alt="The image depicts a range of educational and productivity apps; Readwise, Anki, Quizlet, Apple Notes; alongside an IQ distribution chart, juxtaposing two characters pondering their position within the cognitive spectrum and suggesting themes of self-awareness, identity, and the role of technology in personal growth." src="../img/nitpicky-notes/apple-notes-meme.jpg" /&gt;&lt;/p&gt;</description><author>cmart's blog</author><pubDate>Mon, 16 Sep 2024 02:30:00 GMT</pubDate><guid isPermaLink="true">https://cmart.blog/nitpicky-notes/</guid></item><item><title>Wild Wild Space</title><link>https://olshansky.info/movie/wild_wild_space/</link><description>Olshansky's review of Wild Wild Space</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 15 Sep 2024 18:40:33 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/movie/wild_wild_space/</guid></item><item><title>Reading Highlights 2023</title><link>https://rohitjha.com/blog/reading-highlights-2023/</link><description>&lt;p&gt;2023 had ended up being one of the busiest times in the history of Transcelestial! I have had this post sitting in draft for over a year but I finally have been able to polish it up a bit and release. Just combined my reading highlights from 2023 below. &lt;/p&gt;&lt;p&gt;Previous&lt;/p&gt;</description><author>THINK@RJ</author><pubDate>Sun, 15 Sep 2024 18:30:54 GMT</pubDate><guid isPermaLink="true">https://rohitjha.com/blog/reading-highlights-2023/</guid></item><item><title>Note 150</title><link>https://qubyte.codes/notes/1726398668175</link><description>&lt;p&gt;I’m challenging myself to walk 100k steps this week. I have about 15000 to do today, and I’m half way through that, so I’m taking a coffee break. Portland Coffee in Kemptown, Brighton is super chill. Look at that stained glass, too.&lt;/p&gt;

    &lt;img alt="A photo taken inside the cafe, from the back, looking toward the front. It’s sunny outside and whitewashed walls can be seen opposite, giving it a Mediterranean feel. Above the door is a stained glass window depicting a seagull standing on pebbles, with the sea an a windmill behind. Paintings for sale are on the wall to the right. People sitting by the window are chatting." src="https://qubyte.codes/images/1726398343266.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Sun, 15 Sep 2024 14:11:08 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1726398668175</guid></item><item><title>Glymur Falls</title><link>https://sam.hooke.me/trip/2024/09/glymur-falls/</link><description>&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/glymur/1200/waterfall_in_shadows.jpg"&gt;
 
 &lt;img alt="Photo looking into a canyon cast in shadows, with a 200m waterfall hiding in the back." src="https://sam.hooke.me/images/trip/2024/glymur/1200/waterfall_in_shadows_hu8529513406183925613.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;figcaption style="margin: auto;"&gt;
 
 Glymur Falls hiding in the canyon.
 
 &lt;/figcaption&gt;
 &lt;/div&gt;
&lt;/figure&gt;

&lt;p&gt;Glymur Falls is a popular day hike with stunning views of Iceland&amp;rsquo;s second tallest waterfall. The main trail passes through a small cave, has two river crossings and follows the edge of a deep canyon. The trail is fairly challenging, with a relentless 1,200ft ascent that often hugs sheer drops, so I would not recommend it for new hikers. If you&amp;rsquo;re not able to cross the river, perhaps due to recent heavy rainfall, then there are still grand views from the west side of the canyon, though the waterfall itself is not visible.&lt;/p&gt;</description><author>Sam Hooke</author><pubDate>Sun, 15 Sep 2024 13:30:00 GMT</pubDate><guid isPermaLink="true">https://sam.hooke.me/trip/2024/09/glymur-falls/</guid></item><item><title>Emacs 30.1 highlight - intuitive tab line</title><link>https://srijan.ch/emacs-30-1-highlight-intuitive-tab-line</link><description>Tabs in Emacs 30.1 behave similarly to other common desktop applications</description><author>Srijan Choudhary, all posts</author><pubDate>Sun, 15 Sep 2024 07:45:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/emacs-30-1-highlight-intuitive-tab-line</guid></item><item><title>On this day, September 15</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-15/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2010 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Sun, 15 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-15/</guid></item><item><title>One in ten</title><link>https://eftegarie.com/one-in-ten/</link><description>A long time ago, a young Japanese man decided to start his own bar. His bar did well, even though he noticed that only a tenth of the people liked his bar enough to come back. The other nine never returned. But that’s all he needed to sustain his business—one in ten. He ruminated on [&amp;#8230;]</description><author>Amin Eftegarie</author><pubDate>Sun, 15 Sep 2024 03:40:22 GMT</pubDate><guid isPermaLink="true">https://eftegarie.com/one-in-ten/</guid></item><item><title>Respektovat a být respektován</title><link>https://vit.baisa.cz/books/respektovat-a-byt-respektovan/</link><description>2. kapitola Nevnucovat uspokojování potřeb, které vyhovuje nám.
Neuspokojení potřeb má vždy neg. důsledky.
Dávat všem stejně může být velmi nespravedlivé.</description><author>Vít Baisa</author><pubDate>Sun, 15 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/books/respektovat-a-byt-respektovan/</guid></item><item><title>Reactive Relational Algebra</title><link>https://taylor.town/reactive-relational-algebra</link><description>databases plus wibbly wobbly timey wimey stuff</description><author>taylor.town</author><pubDate>Sun, 15 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/reactive-relational-algebra</guid></item><item><title>Confidence Intervals and Coverage</title><link>https://trigonaminima.github.io/2024/09/confidence-intervals-and-coverage/</link><description>Confidence Interval (CI)</description><author>Playground</author><pubDate>Sun, 15 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://trigonaminima.github.io/2024/09/confidence-intervals-and-coverage/</guid></item><item><title>reMarkable Paper Pro</title><link>https://iam.mt/remarkable-paper-pro/</link><description>I got the new reMarkable Paper Pro and I like it. It fits well with my use cases.</description><author>Mohnish Thallavajhula</author><pubDate>Sun, 15 Sep 2024 02:50:33 GMT</pubDate><guid isPermaLink="true">https://iam.mt/remarkable-paper-pro/</guid></item><item><title>I am Herman Melville</title><link>https://nicolaiarocci.com/i-am-herman-melville/</link><description>&lt;p&gt;I never knew about the connection between Ray Bradbury, John Huston, and Herman Melville.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Today, few people are aware that Bradbury, renowned science fiction writer, beloved fantasist, and mainstay on banned-book lists, wrote the screenplay for the 1956 John Huston adaptation of the Melville classic, which starred Gregory Peck as the iconic and obsessive Captain Ahab. Writing the screenplay was a dream come true for Bradbury, until it morphed into a waking nightmare. As the old adage goes: Never meet your heroes.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Sat, 14 Sep 2024 11:22:27 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/i-am-herman-melville/</guid></item><item><title>My blog successfully survived a scheduled power outage</title><link>https://ounapuu.ee/posts/2024/09/14/look-ma-no-downtime/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/09/14/look-ma-no-downtime/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;I had the opportunity to test the resiliency of my home server setup due to a scheduled power outage on 2024-09-13.&lt;/p&gt;
&lt;p&gt;It was also Friday the 13th. &lt;a href="https://youtu.be/39uoolmtjSA"&gt;I&amp;rsquo;m not superstitious, but I&amp;rsquo;m a little stitious.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;My setup usually consists of the home server, a Wifi AP/router combo box, a converter box for the fiber line, and a
CyberPower UT850EG UPS.&lt;/p&gt;
&lt;p&gt;The planned power outage was communicated a week in advance and was supposed to take up to two hours. It ended up taking
about 1 hour 20 minutes.&lt;/p&gt;
&lt;p&gt;The CyberPower UPS is good for temporary loss of power and can probably run my whole networking and home server setup
for about 10-15 minutes, which is not enough. Luckily I have one of those Jackery power banks that can provide 230V
output
for a much longer time. During a trial run, I found that this Jackery box can sustain my homelab for about 4 hours
without
any issues.&lt;/p&gt;
&lt;p&gt;At 11:02, the power went out and UPS did its thing and kept the setup alive. I then turned on the Jackery power bank,
connected the UPS to the 230 V power line and everything remained operational. I continued working on my laptop
as usual.&lt;/p&gt;
&lt;p&gt;My blog and all the other services I run on my home server kept working.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/14/look-ma-no-downtime/media/grafana.png"&gt;
        &lt;img alt="Great success!" height="289" src="https://ounapuu.ee/posts/2024/09/14/look-ma-no-downtime/media/grafana_hu_24ed6d06f5baca50.png" style="width: auto; height: auto; border-radius: 8px;" width="1000" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Great success!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;I hope to one day do something similar but on a bigger scale, involving solar panels and big batteries. Would be
wicked cool to keep a whole building running off of batteries during outages.&lt;/p&gt;</description><author>./techtipsy</author><pubDate>Sat, 14 Sep 2024 11:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/09/14/look-ma-no-downtime/</guid></item><item><title>Advance of AI</title><link>https://jorin.me/advance-of-ai/</link><description>AI is here to stay. What does that mean for us humans?</description><author>jorin.me</author><pubDate>Sat, 14 Sep 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://jorin.me/advance-of-ai/</guid></item><item><title>On this day, September 14</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-14/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2008 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Sat, 14 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-14/</guid></item><item><title>Scientific Method for News Reading and Political Thought</title><link>https://avodonosov.blogspot.com/2024/09/scientific-method-for-news-reading-and.html</link><description>&lt;p&gt;&lt;i&gt;I drafted this text around 10 years ago, but was not satisfied, hoping to finish it. However didn't have time and energy to complete. So I just post it as as. Maybe will edit later.&lt;/i&gt;&lt;br /&gt;&lt;br /&gt;Disclaimer: banalities and naivety to be expected.&lt;br /&gt;&lt;br /&gt;People fall into all kinds of believes:&lt;br /&gt;one race is superior to another, private property is the root of evil,&lt;br /&gt;Trump is a Russian spy, US 2020 election was forged,&lt;br /&gt;Putin is behind the migrant crisis on Polish / Belarus border,&lt;br /&gt;the financial system is a conspiracy run against us all,&lt;br /&gt;global warming is caused by human activity, COVID-19 pandemic was&lt;br /&gt;intentionally induced by the world rulers.&lt;br /&gt;&lt;br /&gt;How to know what to believe?&lt;br /&gt;&lt;br /&gt;There is an opinion that the most efficient way of studying the reality is the&lt;br /&gt;Scientific Method, which allowed us in just several hundred years to acquire&lt;br /&gt;more knowledge than in all previous tens of thousand years of human history.&lt;br /&gt;&lt;br /&gt;The essence of the Scientific Method is that if we have an explanation&lt;br /&gt;consistent with all the observed facts in the area of interest, this explanation is not&lt;br /&gt;considered true. It's merely a hypothesis. Why? Because there might be other explanations&lt;br /&gt;of the same facts.&lt;br /&gt;&lt;br /&gt;[TODO: Illustrations]&lt;br /&gt;&lt;br /&gt;After coming up with a hypothesis, scientists then try to test it by looking for&lt;br /&gt;new facts that contradict the hypothesis. Often this is done by conducting experiments&lt;br /&gt;about predictions following from the hypothesis.&lt;br /&gt;&lt;br /&gt;Example: If light has mass, then during solar eclipse the starts nearby the Sun&lt;br /&gt;should be observed at positions different than when the Sun is at other&lt;br /&gt;place of the sky, because the Sun's gravity should change the trajectory&lt;br /&gt;of the light from those starts. Try to observe this, if not happens in reality,&lt;br /&gt;the hypothesis is disproved, if happens - it may speak in favor of the hypothesis;&lt;br /&gt;but also may have some other explanation.&lt;br /&gt;&lt;br /&gt;Only after the hypothesis sustained extensive testing that way it becomes a theory.&lt;br /&gt;(But never absolute truth).&lt;br /&gt;&lt;br /&gt;Someone can ask: "in many cases experiments about global social and&lt;br /&gt;political reality will be impossible, because we don't control it".&lt;br /&gt;But if the scientific method mindset is chosen, clever tricks can be invented.&lt;br /&gt;At first thought, how can one weight the Earth?&lt;br /&gt;But &lt;a href="https://en.wikipedia.org/wiki/Cavendish_experiment"&gt;https://en.wikipedia.org/wiki/Cavendish_experiment&lt;/a&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;Moreover, today's Internet is an ocean of facts. Experiments may be not needed,&lt;br /&gt;just search for facts that can correct your current understanding.&lt;br /&gt;&lt;br /&gt;And that's the main practical take-away for me. When reading&lt;br /&gt;about a news topic interesting to me, I intentionally look for information&lt;br /&gt;that contradicts my current view of the topic.&lt;br /&gt;&lt;br /&gt;Then comes fact checking of course.&lt;br /&gt;&lt;br /&gt;But not just fact checking of the supportive evidence that formed the current view.&lt;br /&gt;It's important to search specifically for contradicting information.&lt;br /&gt;&lt;br /&gt;Classic example: if the hypothesis is that all swans are white,&lt;br /&gt;looking for more and more white swans is almost useless.&lt;br /&gt;Instead, we should intentionally search for swans of other colors.&lt;br /&gt;&lt;br /&gt;Such corrective facts are the most informative (&lt;a href="https://en.wikipedia.org/wiki/Quantities_of_information"&gt;https://en.wikipedia.org/wiki/Quantities_of_information&lt;/a&gt;).&lt;br /&gt;Therefore, the scientific method allows one to maximize&lt;br /&gt;the correctness of his understanding of the reality, while minimizing &lt;br /&gt;the efforts needed.&lt;br /&gt;&lt;br /&gt;Being the shortest path to the truth, it's still a difficult&lt;br /&gt;exercise for a single person to study every question up to complete&lt;br /&gt;understanding.&lt;br /&gt;&lt;br /&gt;A collaborative medium would probably be useful (a wiki with certain&lt;br /&gt;agreements or even a specialized structured discussion system).&lt;br /&gt;&lt;br /&gt;Still, even practiced personally, this approach often helps to easily&lt;br /&gt;reject some false views imposed by propaganda, &lt;br /&gt;and to keep the remaining views critically questioned.&lt;br /&gt;&lt;br /&gt;Selective presentation of facts is one of the main methods of&lt;br /&gt;manipulation and propaganda today.&lt;br /&gt;It allows to maintain false picture using true facts (only let&lt;br /&gt;people see facts consistent with this false hypothesis).&lt;/p&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj63PYa-bwFInz5XaIkl0N6AarGy_QI2xtm_7d97pvW49ZQhTGlcRcbA4Q8MvuFL9zuRPn4T_n5E8Ej5FiHF0VbGx43FYtPMneLYSMkG0cGh9AwtLaCUoK7TmsPcde4863QYnWMKv8hc9BZpN4NokaF_eQjn2Ncm_Mb8MHMeJUu_k8YD_1njp-hA-s6PvGh/s640/reality.png" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj63PYa-bwFInz5XaIkl0N6AarGy_QI2xtm_7d97pvW49ZQhTGlcRcbA4Q8MvuFL9zuRPn4T_n5E8Ej5FiHF0VbGx43FYtPMneLYSMkG0cGh9AwtLaCUoK7TmsPcde4863QYnWMKv8hc9BZpN4NokaF_eQjn2Ncm_Mb8MHMeJUu_k8YD_1njp-hA-s6PvGh/w320-h240/reality.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Reality&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7hzBbePrlg1qTBhjoZ2SNXrEcl14aITW2NkPKiIh88VIbQKWlESNH2O9FXYk74MtZkAF4SEstfdg6n27If2qR5OK01nv0Icwdi5_AGM1vXcPCXJj8hsR8NY-ZcpMxG7g4l42nu67osj0fXA1GgGcjeU7edcXygd0iKLeNFt_RGxqxFiPmF94uq9Zqg2LP/s640/triangular-propaganda.png" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj7hzBbePrlg1qTBhjoZ2SNXrEcl14aITW2NkPKiIh88VIbQKWlESNH2O9FXYk74MtZkAF4SEstfdg6n27If2qR5OK01nv0Icwdi5_AGM1vXcPCXJj8hsR8NY-ZcpMxG7g4l42nu67osj0fXA1GgGcjeU7edcXygd0iKLeNFt_RGxqxFiPmF94uq9Zqg2LP/s320/triangular-propaganda.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Propaganda: Don't you see? That's a triangle! Go fact-check our reporting!&lt;/td&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;br /&gt;&lt;/td&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style="text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgT9owUtQp-Sb34XWYjd9aENSEhK7V9FMiTMPsmhRrm6aWmmUQia3xxKnA_hYhreMXMe6iJT9Xcx1QYMscQUzGPckkUBv9nWkI5ZcBmdXRP15oP3LjPv1h5_PVfGwnT09NyAKtXmFhGs_U2Lhvi0bLMUus_2k99qTSD-Z-rbYC3hFUsyh69IeXChK8CNRd4/s640/rectangular-propaganda.png" style="margin-left: auto; margin-right: auto;"&gt;&lt;img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgT9owUtQp-Sb34XWYjd9aENSEhK7V9FMiTMPsmhRrm6aWmmUQia3xxKnA_hYhreMXMe6iJT9Xcx1QYMscQUzGPckkUBv9nWkI5ZcBmdXRP15oP3LjPv1h5_PVfGwnT09NyAKtXmFhGs_U2Lhvi0bLMUus_2k99qTSD-Z-rbYC3hFUsyh69IeXChK8CNRd4/s320/rectangular-propaganda.png" width="320" /&gt;&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td class="tr-caption" style="text-align: center;"&gt;Another propaganda: Don't you see? That's a rectangle! Go fact-check our reporting!&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;br /&gt;The Scientific Method allows to consciously navigate the information space,&lt;br /&gt;instead of passively consuming the huge flood of data poured to us&lt;br /&gt;by media, advertisement and one's social media circle.&lt;br /&gt;&lt;br /&gt;Maybe the general public does not need to know everything,&lt;br /&gt;if there exists a good leadership with adequate world view and values,&lt;br /&gt;who then uses media for the "Manufacture of consent"&lt;br /&gt;(&lt;a href="https://en.wikipedia.org/wiki/Public_Opinion_(book)"&gt;https://en.wikipedia.org/wiki/Public_Opinion_(book)&lt;/a&gt;)&lt;br /&gt;based on false explanations, that are easier for public to accept.&lt;br /&gt;&lt;br /&gt;But how to be sure the leaders are acting in public interest?&lt;br /&gt;&lt;br /&gt;In addition, we know cases in history when the rulers where promoting&lt;br /&gt;crazy ideas and actions.&lt;br /&gt;&lt;br /&gt;And I have impression the today's top politicians and decision makers&lt;br /&gt;often start to believe their own propaganda and loose&lt;br /&gt;connection with reality.&lt;br /&gt;&lt;br /&gt;In history we also know cases when masses taken by ideas overthrow their rulers.&lt;br /&gt;&lt;br /&gt;And today's information technologies allow to amplify false&lt;br /&gt;ideas in social networks, and increase people ability for collective action&lt;br /&gt;and coordination, in particular when pursuing wrong goals.&lt;br /&gt;So the danger of self-induced mass hysteria, xenophobia, instability and violent&lt;br /&gt;uprisings is increasing.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;-----------&lt;br /&gt;todo (nuances to cover):&lt;br /&gt;- The most efficient, but still difficult. At least helps to quickly reject many wrong hypotheses,&lt;br /&gt;&amp;nbsp; and stay questioning the remaining ones.&lt;br /&gt;- general public probably does not need to know everything.&lt;br /&gt;&amp;nbsp; wise leadership, consent formation.&lt;br /&gt;- should all children know Santa Claus does not exist?&lt;br /&gt;- Mass hysteria, lynch courts&lt;br /&gt;- collecting ideas&lt;br /&gt;- collaborative medium&lt;br /&gt;- shortcomings&lt;br /&gt;&amp;nbsp; - Pure rational approach&lt;br /&gt;&amp;nbsp; - "showing the instruments of control"&lt;br /&gt;&amp;nbsp; - core, fundamental values&lt;br /&gt;&amp;nbsp; - proportion of attention to different topics&lt;br /&gt;&amp;nbsp; - are human societies suited to live in knowledge of truth? Or we are better suited to live with illusions?&lt;br /&gt;- irrefutable hypotheses&lt;br /&gt;- genetic algorithms, diversity.&lt;br /&gt;- double blind method for courts&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;</description><author>blog</author><pubDate>Sat, 14 Sep 2024 03:48:22 GMT</pubDate><guid isPermaLink="true">https://avodonosov.blogspot.com/2024/09/scientific-method-for-news-reading-and.html</guid></item><item><title>growing the graveyard of "better spreadsheets"</title><link>https://taylor.town/better-spreadsheets</link><description>spreadsheets remain popular for good reasons</description><author>taylor.town</author><pubDate>Sat, 14 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/better-spreadsheets</guid></item><item><title>Website todo</title><link>https://seirdy.one/meta/website-todo/</link><description>A catalog of all the website improvements I hope to make on seirdy.one, but haven't gotten to yet (and some that I have).</description><author>All content on Seirdy’s Home</author><pubDate>Fri, 13 Sep 2024 22:22:46 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/meta/website-todo/</guid></item><item><title>Introduction to trading for programmers</title><link>https://blog.adnansiddiqi.me/introduction-to-trading-for-programmers/</link><description>&lt;p&gt;I am starting a new series, Trading for Programmers (T4P), where I will cover topics related to trading and how programmers can leverage their existing skills. This is the first post in the series. Why Programmers Should Explore Trading Before diving into trading, it&amp;#8217;s important to understand why programmers should consider getting into it, even if they aren&amp;#8217;t particularly fond of trading. Trading might seem like a world of its own, but it’s actually a great fit for those skilled in computers and numbers. At its core, trading involves buying and selling assets like stocks or cryptocurrencies to make a profit. For programmers, this can be especially intriguing because it’s akin to solving a puzzle with real-world outcomes. Why is trading appealing for programmers? Problem-Solving Skills: Just like coding, trading requires identifying patterns and making decisions based on data. It’s a great way to apply your logical thinking in a different context. Automation:  Programmers love automating tasks, and trading is no exception. You can write code to automate trades, creating your own trading bots to handle the work for you. Data Crunching: If you enjoy working with data, trading offers a chance to analyze numbers and trends to make smart decisions. You can use your data skills to predict market movements and find good trading opportunities. Earn Money: You can earn money by developing your own trading algorithms, taking on freelance projects to build trading tools, or offering consulting services to others in the trading space. If you search on websites like Upwork, you can easily get gigs that are in the range of $40-$150 per hour, sometimes even more! Hope it has sparked a bit of interest in you about trading, so now let&amp;#8217;s talk about what is trading all about and other related things. What is Trading Trading in simple language is all about buying securities: stocks, currencies, crypto, commodities, bonds, and derivatives. Traders can include individuals like you and me, financial institutions like banks, mortgage companies, investment banks, or institutional investors like hedge funds, insurance companies, and pension funds. Stock Market History Although today multiple markets exist, it wasn&amp;#8217;t always that way. Here&amp;#8217;s a timeline of major events in the stock market&amp;#8217;s history: Late 1400s: Antwerp, now in Belgium, becomes a major hub for international trade. Merchants begin buying goods in anticipation of price increases, with some early bond trading also taking place. 1611: Amsterdam sees the creation of the first modern stock exchange with the Dutch East India Company. It becomes the first publicly traded company and dominates trading activity for years. Late 1700s: A group of merchants signs the Buttonwood Tree Agreement, marking the beginning of organized stock trading. This practice eventually evolved into what is now the New York Stock Exchange. 1790: The Philadelphia Stock Exchange is established, contributing to the growth of the U.S. financial sector and supporting the nation&amp;#8217;s westward expansion. 1896: The Dow Jones Industrial Average is introduced, initially featuring 12 companies primarily from the industrial sector. 1923: The precursor to the S&amp;#38;P 500 is created by Henry Barnum Poor’s company, Poor’s Publishing. The index started tracking 90 stocks in 1926. Types of Trading Day Trading: Buying and selling assets within the same trading day to profit from short-term price movements. Swing Trading: Holding positions for several days to capitalize on short- to medium-term trends. Scalping: Making numerous trades in a day to capture small price changes and accumulate profits. Position Trading: Holding assets for weeks, months, or even years based on long-term trends and fundamentals. Algorithmic Trading: Using computer programs and algorithms to execute trades based on predefined criteria. High-Frequency Trading (HFT): Executing a large number of trades at extremely high speeds using advanced algorithms. Trend Following: Trading based on the direction of market trends, buying in uptrends and selling in downtrends. Contrarian Trading: Taking positions that go against prevailing market sentiment, betting that trends will reverse. Position in Trading In trading and investing, a position refers to the amount of a particular asset that an individual or entity owns or has committed to in the market. Positions can be categorized into two main types: Long Position: This is when a trader or investor buys an asset with the expectation that its price will rise. They hold the asset with the intent of selling it later at a higher price for a profit. Short Position: This is when a trader or investor sells an asset they do not own, intending to buy it back at a lower price. They borrow the asset to sell it and hope to repurchase it later at a lower price, profiting from the difference. Positions reflect whether someone is betting on the market to go up or down and determine the exposure to market movements. Assets In trading, an asset is anything you can buy or sell to make money. It could be something like a stock in a company, a piece of real estate, or even a cryptocurrency like Bitcoin. Essentially, it&amp;#8217;s something valuable that you own or trade with the hope that its value will increase so you can sell it for a profit. Here are some common types of assets: Stocks: Shares of ownership in a company, representing a claim on its earnings and assets. Bonds: Debt securities issued by governments or corporations that pay interest over time and return the principal at maturity. Real Estate: Physical properties like land, residential, or commercial buildings that can be bought, sold, or rented. Commodities: Raw materials or primary agricultural products, such as gold, oil, or wheat, that are traded on exchanges. Cryptocurrencies: Digital or virtual currencies using cryptography for security, like Bitcoin or Ethereum. Mutual Funds: Investment funds that pool money from many investors to buy a diversified portfolio of stocks, bonds, or other securities. ETFs (Exchange-Traded Funds): Investment funds traded on stock exchanges, similar to stocks, that hold a variety of assets like stocks or bonds. Cash and Cash Equivalents: Liquid assets like cash itself or short-term investments are easily converted to cash, such as Treasury bills. Derivatives: Financial contracts whose value is derived from the performance of underlying assets, such as options or futures contracts. Trading Analysis and its Types In trading, there are primarily three types of analysis that traders use to make informed decisions: technical analysis, fundamental analysis, and sentiment analysis. Each of these approaches offers unique insights into market behavior and can be used individually or in combination for a well-rounded trading strategy: Fundamental Analysis Fundamental analysis focuses on evaluating a security&amp;#8217;s intrinsic value by analyzing financial statements, economic indicators, and other qualitative and quantitative factors. Key aspects of fundamental analysis include: Company Financials: In stock trading, this involves examining earnings, revenue, and debt-to-equity ratios to assess a company&amp;#8217;s health. Economic Indicators: For forex, analyzing interest rates, employment data, and inflation reports. Market Sentiment: Understanding how broader economic factors, like geopolitical events, affect markets. Technical Analysis Technical analysis involves evaluating past market data, primarily price and volume, to forecast future price movements. Traders use various tools, such as charts, indicators (e.g., RSI, MACD), and patterns (e.g., head and shoulders, triangles), to identify trends and potential trading opportunities. Key components of technical analysis include: Price Action: Analyzing historical prices to spot trends and reversals. Indicators and Oscillators: Tools like moving averages, Bollinger Bands, and stochastic oscillators that help identify overbought or oversold conditions. Chart Patterns: Recognizing formations like double tops, flags, and wedges that suggest potential market moves Sentiment Analysis Sentiment analysis involves gauging the overall mood of the market or crowd to predict how future price movements might unfold. This approach looks at how optimistic or pessimistic traders feel about a particular asset or market in general. Sentiment can be measured using various tools, such as the put/call ratio, VIX (volatility index), or surveys. Key components of sentiment analysis include: News Analysis: Monitoring headlines, social media, and public sentiment to gauge market mood. Contrarian Indicators: Tools like the Fear and Greed Index that highlight when the market may be overly emotional, suggesting a possible correction or reversal.. Signals In trading, &amp;#8220;signals&amp;#8221; are indicators or clues that help you decide when to buy or sell an asset. These signals can come from various sources, like charts showing price movements, news about a company, or patterns that suggest a price might go up or down. Think of signals as helpful hints or tips that guide your trading decisions, making it easier to spot good opportunities and avoid mistakes. Trading Strategy A trading strategy is a plan you follow to decide about buying and selling assets. Think of it like a set of rules or guidelines that help you decide when to enter or exit a trade. This plan is based on things like market trends, price patterns, or specific goals you have. Having a strategy helps you make more consistent and informed decisions, rather than just guessing or reacting to the market at the moment. Conclusion In this post, we discussed the basic terminologies related to trading that will be helpful in future posts. If you’re curious, you can learn more about them on YouTube or other resources. My purpose was to provide a basic introduction to these terms. Stay tuned, as we&amp;#8217;ll now cover how you can use your programming skills in the world of trading. If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/introduction-to-trading-for-programmers/"&gt;Introduction to trading for programmers&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Fri, 13 Sep 2024 22:00:31 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/introduction-to-trading-for-programmers/</guid></item><item><title>Aurora Borealis</title><link>https://sam.hooke.me/trip/2024/09/aurora-borealis/</link><description>&lt;p&gt;During our stay in Iceland, in a rural area 20 minutes north of Selfoss, we were fortunate enough to see the Aurora Borealis (Northern Lights) two nights in a row. The skies were relatively clear, and there was a solar storm with a Kp-index of between 6 to 8 out of 9. These are some of my favourite photos:&lt;/p&gt;



 




&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 &lt;img alt="Time-lapse video looking straight up at stars with a green and purple aurora passing overhead." src="https://sam.hooke.me/images/trip/2024/aurora/animation_20pc_100ms.webp" /&gt;


 &lt;figcaption style="margin: auto;"&gt;
 
 This time-lapse shows 7 minutes of aurora activity in 6 seconds, so is 70x faster than real life. It consists of 60 individual photos taken 7 seconds apart. The camera remained stationary, but enough time passes that you can see the stars move overhead.
 
 &lt;/figcaption&gt;
 &lt;/div&gt;
&lt;/figure&gt;



&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_01.jpg"&gt;
 
 &lt;img alt="Photo of green aurora over cabin." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_01_hu918179913453381669.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_02.jpg"&gt;
 
 &lt;img alt="Photo of green aurora over mountains." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_02_hu6711727079994856080.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_03.jpg"&gt;
 
 &lt;img alt="Photo looking straight up at stars with the aurora as a faint diagonal white line." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_03_hu8164941598153846057.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_04.jpg"&gt;
 
 &lt;img alt="Photo looking straight up at stars with the aurora as a bright diagonal white line." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_04_hu8894514849680792705.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_05.jpg"&gt;
 
 &lt;img alt="Photo of green aurora over mountains." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_05_hu13714165919073337390.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_06.jpg"&gt;
 
 &lt;img alt="Photo looking straight up at bright white aurora." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_06_hu1906940078038887365.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_07.jpg"&gt;
 
 &lt;img alt="Photo looking straight up at bright green and red aurora." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_07_hu4560316107354280711.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_08.jpg"&gt;
 
 &lt;img alt="Photo of green 'S' shaped aurora over mountains." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_08_hu9171676471496672197.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_09.jpg"&gt;
 
 &lt;img alt="Photo of bright green and red aurora over lights." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_09_hu5639880330469518069.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_10.jpg"&gt;
 
 &lt;img alt="Photo of faint green and red aurora over lights." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_10_hu4990525496611416750.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_11.jpg"&gt;
 
 &lt;img alt="Photo of faint green and red aurora over lights." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_11_hu14391580650437214540.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_12.jpg"&gt;
 
 &lt;img alt="Photo of faint green and red aurora over lights." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_12_hu5235446213241232754.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_13.jpg"&gt;
 
 &lt;img alt="Photo of green aurora over mountains." src="https://sam.hooke.me/images/trip/2024/aurora/1200/aurora_13_hu15674068600199898403.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The photos were taken by shooting manually, with the following settings:&lt;/p&gt;</description><author>Sam Hooke</author><pubDate>Fri, 13 Sep 2024 13:55:00 GMT</pubDate><guid isPermaLink="true">https://sam.hooke.me/trip/2024/09/aurora-borealis/</guid></item><item><title>Books are strange objects</title><link>https://nicolaiarocci.com/books-are-strange-objects/</link><description>&lt;p&gt;Dave Rupert, reasoning on why he likes books:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Books are strange objects. Chapters and chapters of coherent research and lived experiences assembled by people who wanted to put it all down in one place. Edited by actual editors who like editing. Designed— down to the weight of the paper, the typography, and the illustration on the cover— to make the experience of reading it enjoyable. Books are uncanny and impractical objects. A terribly inefficient way to encode information from one brain to another, but an excellent way to tell a story.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Fri, 13 Sep 2024 10:14:43 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/books-are-strange-objects/</guid></item><item><title>Just Do It</title><link>https://martinrue.com/just-do-it</link><description>13 years ago I wrote my first ever article, “Do”. It tells the story of how my mother became really good at Counter Strike. But less about her… I want to talk about a murderer.</description><author>Martin Rue</author><pubDate>Fri, 13 Sep 2024 10:00:00 GMT</pubDate><guid isPermaLink="true">https://martinrue.com/just-do-it</guid></item><item><title>On this day, September 13</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-13/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2012 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Fri, 13 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-13/</guid></item><item><title>Tracking, not Tracing, Linux Thread Activity for Complete System Visibility (eBPF Summit)</title><link>https://tanelpoder.com/posts/tracking-not-tracing-linux-thread-activity-with-ebpf/</link><description>&lt;p&gt;Here&amp;rsquo;s my talk from &lt;a href="https://ebpf.io/summit-2024/"&gt;eBPF Summit 2024&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="video-10-minutes"&gt;Video (10 minutes)&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://0x.tools"&gt;0x.tools&lt;/a&gt; &lt;strong&gt;Extended Task State Sampling&lt;/strong&gt; approach provides a new observability signal - wall-clock time of all active threads&amp;rsquo; activity in your system. You can see both total workload &lt;em&gt;demand&lt;/em&gt; of your apps and also drill down deep into individual threads&amp;rsquo; activity when needed. This method and toolset give you a single data source with many &lt;em&gt;directly linked&lt;/em&gt; fields of information about what your application &amp;amp; OS threads are doing and why.&lt;/p&gt;</description><author>Tanel Poder Blog</author><pubDate>Fri, 13 Sep 2024 03:55:07 GMT</pubDate><guid isPermaLink="true">https://tanelpoder.com/posts/tracking-not-tracing-linux-thread-activity-with-ebpf/</guid></item><item><title>Lose-able keys are a feature</title><link>https://seirdy.one/notes/2024/09/12/loseable-keys-are-a-feature/</link><description>&lt;p&gt;In opsec, &lt;a href="https://xkcd.com/538/"&gt;duress (“rubber-hose”) attacks are famously hard to address&lt;/a&gt;. Cryptographic keys that cannot be lost have poor protections against duress.&lt;/p&gt;&lt;p&gt;Travelers can leave key fobs at home should they be accosted. A victim of a break-in can conveniently “lose” or smash a hardware key, erasing any encrypted data. Yes, I know about cold-boot attacks; I don’t recommend at-risk people to leave things decrypted for long durations. I like the idea of spring-loaded key fobs that can’t be left plugged in.&lt;/p&gt;&lt;p&gt;People talking about key fob body implants don’t usually plan for removing them in seconds with plausible deniability.&lt;/p&gt;</description><author>All content on Seirdy’s Home</author><pubDate>Fri, 13 Sep 2024 03:39:41 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/notes/2024/09/12/loseable-keys-are-a-feature/</guid></item><item><title>Tiny Great Languages: APL</title><link>https://zserge.com/posts/langs-apl/</link><description>This is part 5 from series &amp;ldquo;Tiny Great Languages&amp;rdquo;.
Final code is on Github. Part 1: Assembly. Part 2: BASIC. Part 3: Forth/MOUSE. Part 4: Lisp. Part 5: APL/K. Part 6: PL/0. This would be a controversial language, but it fits perfectly into the 50-lines-of-less code category. Let&amp;rsquo;s talk about APL family, and specifically &amp;ndash; K.
Created by Arthur Whitney the language is known for its terse and cryptic syntax.</description><author>zserge's blog</author><pubDate>Fri, 13 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zserge.com/posts/langs-apl/</guid></item><item><title>a one man war of attrition</title><link>https://taylor.town/attrition</link><description>attrition warfare is tiring</description><author>taylor.town</author><pubDate>Fri, 13 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/attrition</guid></item><item><title>Fixing drag events with Tauri</title><link>https://ellie.wtf/notes/drag-event-issues-in-tauri/</link><description>&lt;p&gt;I've been working on a desktop app with Tauri, and had issues for a while with the "draggable" prop on some elements. Instead of them dragging as I expected, I'd just get a plus icon.&lt;/p&gt;
&lt;p&gt;The fix was pretty easy&lt;/p&gt;
&lt;p&gt;I added&lt;/p&gt;
&lt;pre class="giallo" style="color: #EBDBB2; background-color: #32302F;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;app: {&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	window:[{&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;		...snip,&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;		&amp;quot;dragDropEnabled&amp;quot;: false&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;	}]&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to my &lt;code&gt;tauri.config.json&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is mentioned in the Tauri docs: https://v2.tauri.app/reference/javascript/api/namespacewebview/#properties-1&lt;/p&gt;</description><author>Ellie's Notes</author><pubDate>Fri, 13 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ellie.wtf/notes/drag-event-issues-in-tauri/</guid></item><item><title>Practical Cybersafety for Everyone</title><link>https://jasoneckert.github.io/myblog/cybersafety/</link><description>&lt;p&gt;&lt;img alt="Cybersafety" src="cybersafety.jpg#center" title="Cybersafety" /&gt;&lt;/p&gt;
&lt;p&gt;The Internet is a dangerous place. In this post, I’ll cover five basic Cybersafety tips that anyone (including those who aren&amp;rsquo;t tech-savvy) can use to stay safe online.&lt;/p&gt;
&lt;h1 id="1-protect-against-viruses-and-malware"&gt;1. Protect against viruses and malware&lt;/h1&gt;
&lt;p&gt;Most computer &lt;em&gt;&lt;strong&gt;viruses&lt;/strong&gt;&lt;/em&gt; are small sections of code that are embedded into files such as PowerPoint presentations and PDFs. When these files are opened in a software app (e.g., Microsoft PowerPoint or Adobe Reader), the app inadvertently executes the malicious code. Alternatively, &lt;em&gt;&lt;strong&gt;malware&lt;/strong&gt;&lt;/em&gt; (short for malicious software) includes apps that often run silently in the background to perform malicious tasks, such as stealing information or encrypting files until you pay a ransom (called &lt;em&gt;&lt;strong&gt;ransomware&lt;/strong&gt;&lt;/em&gt;). As a result, malware is one of the most dangerous threats to computer users today, and many viruses merely download and execute malware from Internet sites.&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Fri, 13 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/cybersafety/</guid></item><item><title>MoveMe - SwiftUI Edition</title><link>https://whackylabs.com/swift/swiftui/ios/animation/2024/09/12/moveme-swiftui-edition/</link><description>&lt;p&gt;Taking about gestures and animation with SwiftUI is actually not as intuitive as it sounds. But how hard could it be?&lt;/p&gt;

&lt;p&gt;&lt;img alt="Best way to build an app is with Swift and SwiftUI" src="/assets/moveme-swiftui/meme.jpg" /&gt;&lt;/p&gt;

&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;

&lt;p&gt;So like always we need 3 squares nicely lined up in the center of the screen. I’m going to use &lt;code class="language-plaintext highlighter-rouge"&gt;ZStack&lt;/code&gt; because I want the squares to layout independently of each other. The only thing the parent container view needs to provide is the initial position.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;
  
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;ZStack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;ForEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
        &lt;span class="kt"&gt;GeometryReader&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;geometry&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
          &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;geometry&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CGFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mf"&gt;4.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="setup" src="https://whackylabs.com/assets/moveme-swiftui/01-setup.png" /&gt;&lt;/p&gt;

&lt;h3 id="gestures"&gt;Gestures&lt;/h3&gt;
&lt;p&gt;Next we need a tap gesture and when detected the selected square should become red. The obvious solution would be to use a &lt;code class="language-plaintext highlighter-rouge"&gt;TapGesture&lt;/code&gt; but the &lt;code class="language-plaintext highlighter-rouge"&gt;TapGesture&lt;/code&gt; only activates at touch end and what we need is a way to detect touch began. The actual solution I found is to use the &lt;code class="language-plaintext highlighter-rouge"&gt;DragGesture&lt;/code&gt; with &lt;code class="language-plaintext highlighter-rouge"&gt;minimumDistance&lt;/code&gt; set to &lt;code class="language-plaintext highlighter-rouge"&gt;0&lt;/code&gt;.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;red&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;DragGesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;minimumDistance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChanged&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEnded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="touch" src="https://whackylabs.com/assets/moveme-swiftui/02-touch.gif" /&gt;&lt;/p&gt;

&lt;p&gt;Next we need to square to scale up when selected. Again the obvious solution is to use the &lt;code class="language-plaintext highlighter-rouge"&gt;scaleEffect&lt;/code&gt;.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;red&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scaleEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;DragGesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;minimumDistance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChanged&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEnded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But this brings another problem. Notice how the squares are not centered when scaling up.&lt;/p&gt;

&lt;p&gt;&lt;img alt="scale-bug" src="https://whackylabs.com/assets/moveme-swiftui/03-scale-bug.gif" /&gt;&lt;/p&gt;

&lt;p&gt;This is because the view tree in SwiftUI is inverted because each modifier creates a new &lt;code class="language-plaintext highlighter-rouge"&gt;View&lt;/code&gt; and wraps the invoking object as its child. So these two are equivalent:&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kt"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;firstModifier&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
 &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;secondModifier&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kt"&gt;SecondModifierView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="kt"&gt;FirstModifierView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="kt"&gt;Box&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So back in our solution above the &lt;code class="language-plaintext highlighter-rouge"&gt;scaleEffect&lt;/code&gt; is applied first and then the &lt;code class="language-plaintext highlighter-rouge"&gt;position&lt;/code&gt;. This make the center to become off centered. 
The solution is to apply the &lt;code class="language-plaintext highlighter-rouge"&gt;position&lt;/code&gt; modifier after the &lt;code class="language-plaintext highlighter-rouge"&gt;scaleEffect&lt;/code&gt;.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scaleEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;red&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;DragGesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;minimumDistance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChanged&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEnded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="scale" src="https://whackylabs.com/assets/moveme-swiftui/04-scale.gif" /&gt;&lt;/p&gt;

&lt;p&gt;And finally we want the square to move around with the finger. This part is simple since we already have drag gesture, we just need to update the position of the square.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scaleEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;red&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;DragGesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;minimumDistance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChanged&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEnded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="drag" src="https://whackylabs.com/assets/moveme-swiftui/05-drag.gif" /&gt;&lt;/p&gt;

&lt;h3 id="animations"&gt;Animations&lt;/h3&gt;
&lt;p&gt;For the next part we would like the transitions to animate. For this we can either use the &lt;code class="language-plaintext highlighter-rouge"&gt;withAnimation&lt;/code&gt; block&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scaleEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;red&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;DragGesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;minimumDistance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChanged&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="n"&gt;withAnimation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEnded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="n"&gt;withAnimation&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But look our favorite bug is back again. Notice how the squares are off centered when selected.&lt;/p&gt;

&lt;p&gt;&lt;img alt="animation-bug" src="https://whackylabs.com/assets/moveme-swiftui/06-animation-bug.gif" /&gt;&lt;/p&gt;

&lt;p&gt;But now we know why this bug exists. We need to guarantee that the position is applied after the scaling. But since we are using the &lt;code class="language-plaintext highlighter-rouge"&gt;withAnimation&lt;/code&gt; block it sets some flag for the next draw pass to be done with animation. And that means all the changes are animated. But what we want is to have only have the scaling change as animated and everything else non animated.&lt;/p&gt;

&lt;p&gt;To achieve this we can use the  &lt;code class="language-plaintext highlighter-rouge"&gt;animation&lt;/code&gt; modifier. It does the same thing as &lt;code class="language-plaintext highlighter-rouge"&gt;withAnimation&lt;/code&gt; block but we can control where in the &lt;code class="language-plaintext highlighter-rouge"&gt;View&lt;/code&gt; hierarchy we want the animation to happen.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;
  &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;RoundedRectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;cornerRadius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;25.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;continuous&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scaleEffect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;anchor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;easeOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;isSelected&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;red&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="kt"&gt;DragGesture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;minimumDistance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onChanged&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onEnded&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
            &lt;span class="n"&gt;isSelected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
          &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="animation" src="https://whackylabs.com/assets/moveme-swiftui/07-animation.gif" /&gt;&lt;/p&gt;

&lt;p&gt;The solution is available on &lt;a href="https://github.com/chunkyguy/MoveMe/tree/main/swiftui"&gt;https://github.com/chunkyguy/MoveMe&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="references"&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2023/10156"&gt;Explore SwiftUI animation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://developer.apple.com/videos/play/wwdc2023/10157/"&gt;Wind your way through advanced animations in SwiftUI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Thu, 12 Sep 2024 21:38:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/swift/swiftui/ios/animation/2024/09/12/moveme-swiftui-edition/</guid></item><item><title>Lifesteal on a Technicality</title><link>https://dustinfreeman.org/blog/lifesteal-tech/</link><description>I am playing Marvel&amp;#8217;s Midnight Suns. Like any big modern game, it is a sloppy mess of systems, for which the balance and neatness of design doesn’t matter as the point is the joy of the complex systems smooshing together. Many characters can take actions to add the &amp;#8220;Lifesteal&amp;#8221; modifier to attacks. Traditionally, Lifesteal (or [&amp;#8230;]</description><author>Dustin Freeman</author><pubDate>Thu, 12 Sep 2024 19:21:25 GMT</pubDate><guid isPermaLink="true">https://dustinfreeman.org/blog/lifesteal-tech/</guid></item><item><title>Under ASP.NET 8, NGINX returns 502 Bad Gateway after authentication by IdentityServer</title><link>https://nicolaiarocci.com/under-aspnet-8-nginx-returns-502-bad-gateway-after-authentication-by-identityserver/</link><description>Today, I learned the hard way that NGINX has default buffer sizes, which can cause trouble in specific scenarios like mine.</description><author>Nicola Iarocci</author><pubDate>Thu, 12 Sep 2024 17:17:58 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/under-aspnet-8-nginx-returns-502-bad-gateway-after-authentication-by-identityserver/</guid></item><item><title>LLM Development Lessons: Tools, Tips, and Tricks</title><link>https://prashamhtrivedi.in/llm_dev_lessons/</link><description>A developer&amp;rsquo;s journey through LLM-powered development tools. Shares favorite tools, reasons for skipping others, and 8 battle-tested tips for LLM coding.</description><author>Prasham H Trivedi</author><pubDate>Thu, 12 Sep 2024 16:25:24 GMT</pubDate><guid isPermaLink="true">https://prashamhtrivedi.in/llm_dev_lessons/</guid></item><item><title>The loneliness of the low ranking tennis player</title><link>https://nicolaiarocci.com/the-loneliness-of-the-low-ranking-tennis-player/</link><description>&lt;p&gt;I admit, like many of my compatriots in this last year and a half, I follow a lot more tennis than usual, and it is all the fault (or merit) of Jannick Sinner. The top-level pro tennis field appears distant, privileged, brilliant and rewarding. We appreciate the immense talent of these players and sympathize with the struggle and stress they undergo. We praise their character, determination, and mental strength. They make a lot of money, so we infer they conduct fulfilling and satisfying lives. Most fans, however, ignore how crowded, harsh, lonely, and unapologetic professional players&amp;rsquo; lives are below the elite.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Thu, 12 Sep 2024 11:35:10 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/the-loneliness-of-the-low-ranking-tennis-player/</guid></item><item><title>On this day, September 12</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-12/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2009 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Thu, 12 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-12/</guid></item><item><title>Making libjson RFC 8259 compliant</title><link>https://xnacly.me/posts/2024/libjson/</link><description>Giving lots of edge cases attention, so I can call my json parser RFC 8259 compliant</description><author>xnacly - blog</author><pubDate>Thu, 12 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2024/libjson/</guid></item><item><title>Tiny Great Languages: Lisp</title><link>https://zserge.com/posts/langs-lisp/</link><description>This is part 4 from series &amp;ldquo;Tiny Great Languages&amp;rdquo;.
Final code is on Github. Part 1: Assembly. Part 2: BASIC. Part 3: Forth/MOUSE. Part 4: Lisp. Part 5: APL/K. Part 6: PL/0. Done with the concatenative language MOUSE, we can now turn our attention to another small and elegant language from way back: Lisp. Lisp is famous for its minimalist syntax (similar to Forth, the parser is almost nonexistent) and its clear, logical evaluation rules.</description><author>zserge's blog</author><pubDate>Thu, 12 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zserge.com/posts/langs-lisp/</guid></item><item><title>Coordinating multiple gesture recognizers</title><link>https://whackylabs.com/swift/uikit/ios/animation/2024/09/11/coordinating-multiple-gesture-recognizers/</link><description>&lt;p&gt;So how does one actually work with multiple gesture recognizers on same view?&lt;/p&gt;

&lt;p&gt;&lt;img alt="UIGestureRecognizer is now my best friend" src="/assets/coordinating-gestures/meme.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;Let’s recreate the &lt;strong&gt;MoveMe&lt;/strong&gt; sample with &lt;code class="language-plaintext highlighter-rouge"&gt;UIGestureRecognizer&lt;/code&gt;. The idea is to have both &lt;code class="language-plaintext highlighter-rouge"&gt;UILongPressGestureRecognizer&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;UIPanGestureRecognizer&lt;/code&gt; play nicely with each other. With &lt;code class="language-plaintext highlighter-rouge"&gt;UILongPressGestureRecognizer&lt;/code&gt; responsible for detecting selection and &lt;code class="language-plaintext highlighter-rouge"&gt;UIPanGestureRecognizer&lt;/code&gt; responsible for dragging the selected squares.&lt;/p&gt;

&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;

&lt;p&gt;We have a &lt;code class="language-plaintext highlighter-rouge"&gt;SquareView&lt;/code&gt; that can react to changes such as selection and change in position. The main updates happen in &lt;code class="language-plaintext highlighter-rouge"&gt;layoutSubviews&lt;/code&gt;. &lt;code class="language-plaintext highlighter-rouge"&gt;setNeedsLayout&lt;/code&gt; reschedules a update at next draw cycle and &lt;code class="language-plaintext highlighter-rouge"&gt;layoutIfNeeded&lt;/code&gt; requests an update immediately.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;SquareViewProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;color&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIColor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SquareViewProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;didSet&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;setNeedsLayout&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SquareViewProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;midX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;midY&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;layoutSubviews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;layoutSubviews&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;color&lt;/span&gt;
    &lt;span class="n"&gt;center&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;
    &lt;span class="n"&gt;transform&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGAffineTransform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;scaleX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;resetProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;SquareViewProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;props&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;
    &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;animate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;withDuration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;beginFromCurrentState&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;layoutIfNeeded&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then we can host the &lt;code class="language-plaintext highlighter-rouge"&gt;SquareView&lt;/code&gt; in some parent &lt;code class="language-plaintext highlighter-rouge"&gt;UIView&lt;/code&gt; or &lt;code class="language-plaintext highlighter-rouge"&gt;UIViewController&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;squareVws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    
    &lt;span class="n"&gt;squareVws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
      &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGRect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nv"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;midX&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nv"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lerp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nv"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minY&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bounds&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;maxY&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nv"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGFloat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
          &lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    
    &lt;span class="n"&gt;squareVws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;  
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="setup" src="https://whackylabs.com/assets/coordinating-gestures/01-setup.png" /&gt;&lt;/p&gt;

&lt;h3 id="problem"&gt;Problem&lt;/h3&gt;

&lt;p&gt;Next we can add a &lt;code class="language-plaintext highlighter-rouge"&gt;UIPanGestureRecognizer&lt;/code&gt; on the &lt;code class="language-plaintext highlighter-rouge"&gt;ViewController&lt;/code&gt; and forward the gesture events to selected views.&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;enum&lt;/span&gt; &lt;span class="kt"&gt;GestureEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;began&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="nf"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ended&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;GestureEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;began&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;resetProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SquareViewProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;
      
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
      &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CGPoint&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;resetProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;SquareViewProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;props&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;squareVws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;selectedVws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;dragGesture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIPanGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;#selector(&lt;/span&gt;&lt;span class="nf"&gt;handleDrag&lt;/span&gt;&lt;span class="kd"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    
    &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dragGesture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  
  &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleDrag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIPanGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;began&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;selectedVws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;squareVws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;began&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;translation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTranslation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ended&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;selectedVws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
      
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  
  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;GestureEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;selectedVws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So far so good but with this implementation we receive gesture events after the drag has started but we want to receive the &lt;code class="language-plaintext highlighter-rouge"&gt;.began&lt;/code&gt; as soon as the user touches the square. We can use the &lt;code class="language-plaintext highlighter-rouge"&gt;UITapGestureRecognizer&lt;/code&gt; but it only activates at touch up and not at touch down. So either we need to rollout our own gesture or ‘hack’ &lt;code class="language-plaintext highlighter-rouge"&gt;UILongPressGestureRecognizer&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;squareVws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;selectedVws&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;SquareView&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// ...&lt;/span&gt;
    
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;tapGesture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UILongPressGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;#selector(&lt;/span&gt;&lt;span class="nf"&gt;handleTap&lt;/span&gt;&lt;span class="kd"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tapGesture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minimumPressDuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;
    
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;dragGesture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIPanGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nv"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;#selector(&lt;/span&gt;&lt;span class="nf"&gt;handleDrag&lt;/span&gt;&lt;span class="kd"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    
    &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tapGesture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dragGesture&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  
  &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleTap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UILongPressGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;began&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;location&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;selectedVws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;squareVws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;began&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;ended&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ended&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;selectedVws&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        
      &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  
  &lt;span class="kd"&gt;@objc&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleDrag&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIPanGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;translation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;changed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;translation&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setTranslation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  
  &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;GestureEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;selectedVws&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forEach&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;handleGestureEvent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;But this poses another problem. On a &lt;code class="language-plaintext highlighter-rouge"&gt;UIView&lt;/code&gt; by default only one gesture recognizer is active at a time. So once the &lt;code class="language-plaintext highlighter-rouge"&gt;UILongPressGestureRecognizer&lt;/code&gt; is activated &lt;code class="language-plaintext highlighter-rouge"&gt;UIPanGestureRecognizer&lt;/code&gt; is ignored.&lt;/p&gt;

&lt;h3 id="solution"&gt;Solution&lt;/h3&gt;
&lt;p&gt;Gesture recognizers have a defined order of precedence. So if multiple gesture recognizers are attached to a view, the winner is decided by the default rules. But we can override the rules by implementing the &lt;code class="language-plaintext highlighter-rouge"&gt;UIGestureRecognizerDelegate&lt;/code&gt;. For our case since each gesture recognizer is listening to different states we can have both the gestures active at the same time&lt;/p&gt;

&lt;div class="language-swift highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="kt"&gt;ViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIViewController&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  
  &lt;span class="c1"&gt;// ...    &lt;/span&gt;
  
  
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;viewDidLoad&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;tapGesture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UILongPressGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;dragGesture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIPanGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;// ...    &lt;/span&gt;
    
    &lt;span class="n"&gt;tapGesture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
    &lt;span class="n"&gt;dragGesture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delegate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;extension&lt;/span&gt; &lt;span class="kt"&gt;ViewController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIGestureRecognizerDelegate&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;gestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;gestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;shouldRecognizeSimultaneouslyWith&lt;/span&gt; &lt;span class="nv"&gt;otherGestureRecognizer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIGestureRecognizer&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;gestureRecognizer&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;UILongPressGestureRecognizer&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;otherGestureRecognizer&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="kt"&gt;UIPanGestureRecognizer&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="setup" src="https://whackylabs.com/assets/coordinating-gestures/02-solution.gif" /&gt;&lt;/p&gt;

&lt;p&gt;The solution is available on &lt;a href="https://github.com/chunkyguy/MoveMe/tree/main/uikit"&gt;https://github.com/chunkyguy/MoveMe&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="references"&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers"&gt;Coordinating multiple gesture recognizers&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://developer.apple.com/documentation/uikit/touches_presses_and_gestures/coordinating_multiple_gesture_recognizers/allowing_the_simultaneous_recognition_of_multiple_gestures"&gt;Allowing the simultaneous recognition of multiple gestures&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Wed, 11 Sep 2024 22:53:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/swift/uikit/ios/animation/2024/09/11/coordinating-multiple-gesture-recognizers/</guid></item><item><title>The disposable web</title><link>https://paul.kinlan.me/the-disposable-web/</link><description>Reflecting on my journey with computers, from the C64 and Amiga 500 to the present day, I've found a renewed excitement in software development.  New tools like repl.it and websim.ai empower rapid creation of full-stack, disposable web apps – software built for personal use and easily discarded. This ease of creation removes the barrier to starting projects, making the web an ideal platform for even single-user applications. It's a shift from handcrafted software to a more ephemeral approach, allowing for quicker prototyping and experimentation.</description><author>Modern Web Development with Chrome</author><pubDate>Wed, 11 Sep 2024 22:49:00 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/the-disposable-web/</guid></item><item><title>Geysir</title><link>https://sam.hooke.me/trip/2024/09/geysir/</link><description>&lt;h2 id="strokkur"&gt;Strokkur&lt;/h2&gt;
&lt;p&gt;The main attraction at Geysir is Strokkur, a geyser that supposedly erupts about every 10 minutes. While we were there, it seemed to erupt about every 5 minutes! We saw more than half a dozen eruptions, and the height varied a lot.&lt;/p&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_01.jpg"&gt;
 
 &lt;img alt="Photo of Strokkur geyser erupting (1/9)." src="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_01_hu12857190019504712070.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_02.jpg"&gt;
 
 &lt;img alt="Photo of Strokkur geyser erupting (2/9)." src="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_02_hu9917415700799555823.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_03.jpg"&gt;
 
 &lt;img alt="Photo of Strokkur geyser erupting (3/9)." src="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_03_hu1936184457060762201.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_04.jpg"&gt;
 
 &lt;img alt="Photo of Strokkur geyser erupting (4/9)." src="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_04_hu16793241654382543739.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_05.jpg"&gt;
 
 &lt;img alt="Photo of Strokkur geyser erupting (5/9)." src="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_05_hu9260709933757179836.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_06.jpg"&gt;
 
 &lt;img alt="Photo of Strokkur geyser erupting (6/9)." src="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_06_hu14050159619810328754.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_07.jpg"&gt;
 
 &lt;img alt="Photo of Strokkur geyser erupting (7/9)." src="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_07_hu7814095194137571397.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_08.jpg"&gt;
 
 &lt;img alt="Photo of Strokkur geyser erupting (8/9)." src="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_08_hu7467903188728302209.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_09.jpg"&gt;
 
 &lt;img alt="Photo of Strokkur geyser erupting (9/9)." src="https://sam.hooke.me/images/trip/2024/geysir/1200/strokkur_09_hu17015323185980484085.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;
&lt;h2 id="geysir"&gt;Geysir&lt;/h2&gt;
&lt;p&gt;The namesake of Geysir, this geyser is a stone&amp;rsquo;s throw from Strokkur, but nowadays is dormant. Still, it was exciting to see Geysir up close even though there were no eruptions.&lt;/p&gt;</description><author>Sam Hooke</author><pubDate>Wed, 11 Sep 2024 17:00:00 GMT</pubDate><guid isPermaLink="true">https://sam.hooke.me/trip/2024/09/geysir/</guid></item><item><title>How to refresh cache in MediaWiki</title><link>https://river.me/blog/refreshing-cache/</link><description>Caching can be super complicated in MediaWiki because there are many different types of cache. This article goes over what to do for each kind.</description><author>River Writes - A MediaWiki Blog</author><pubDate>Wed, 11 Sep 2024 09:19:54 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/refreshing-cache/</guid></item><item><title>On this day, September 11</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-11/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2006 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Wed, 11 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-11/</guid></item><item><title>Spiffing up those echo messages</title><link>https://xenodium.com/spiffing-up-those-echo-messages</link><description>&lt;p&gt;Well-ingrained into every Emacs user is the echo area, a one-stop shop to receive any kind of message from the editor, located at the bottom of the frame. Posting messages to this area from elisp couldn't be simpler:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(message &amp;quot;Hello world&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/spiffing-up-those-echo-messages/svg-hello-world.gif" /&gt;&lt;/p&gt;
&lt;p&gt;If we want to get a little fancier, we can propertize the text to add some styling.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(message (propertize &amp;quot;hello &amp;quot; 'face '(:foreground &amp;quot;#C3E88D&amp;quot;))
         (propertize &amp;quot;world&amp;quot; 'face '(:foreground &amp;quot;#FF5370&amp;quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/spiffing-up-those-echo-messages/svg-hello-world-colors.gif" /&gt;&lt;/p&gt;
&lt;p&gt;With this in mind, I set out to add a tiny command to &lt;a href="https://github.com/xenodium/ready-player"&gt;ready-player&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I wanted the ability to ask what's on without switching to another buffer. The echo area is perfect for that. It should display track title, artist, and album.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(message (concat &amp;quot;Ahead &amp;quot; ;; title
                 (propertize &amp;quot;Wire &amp;quot; 'face '(:foreground &amp;quot;#C3E88D&amp;quot;)) ;; artist
                 (propertize &amp;quot;The Ideal Copy&amp;quot; 'face '(:foreground &amp;quot;#FF5370&amp;quot;)))) ;; album
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/spiffing-up-those-echo-messages/svg-ready-player-colors.gif" /&gt;&lt;/p&gt;
&lt;p&gt;This kinda works, but I wasn't convinced with the styling. Maybe I need multi-line?&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(message (concat &amp;quot;Ahead\n&amp;quot; ;; title
                 (propertize &amp;quot;Wire\n&amp;quot; 'face '(:foreground &amp;quot;#C3E88D&amp;quot;)) ;; artist
                 (propertize &amp;quot;The Ideal Copy&amp;quot; 'face '(:foreground &amp;quot;#FF5370&amp;quot;)))) ;; album
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/spiffing-up-those-echo-messages/svg-ready-player-colors-multiline.gif" /&gt;&lt;/p&gt;
&lt;p&gt;I felt something was missing. If I could just add the album artwork as a thumbnail… The ideal layout would maybe look something like:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;+-------+
|       | Ahead
| image | Wire
|       | The Ideal Copy
+-------+
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While the text-everywhere nature of Emacs buffers has many advantages, building more involved layouts can have its challenges. But hey, for that simple read-only message we're aiming at, we can certainly get creative without too much trouble. You see, Emacs has native svg support, so we can craft our fancy layout in elisp and tell Emacs to render it for us.&lt;/p&gt;
&lt;p&gt;While I'm a noob at doing anything in svg from Emacs, adding an image and three labels, really isn't that difficult.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(message
 (let* ((image-width 90)
        (image-height 90)
        (text-height 25)
        (svg (svg-create (frame-pixel-width) image-height)))
   (svg-embed svg &amp;quot;path/to/thumbnail.png&amp;quot;
              &amp;quot;image/png&amp;quot; nil
              :x 0 :y 0 :width image-width :height image-height)
   (svg-text svg &amp;quot;Ahead&amp;quot;
             :x (+ image-width 10) :y text-height
             :fill (face-attribute 'default :foreground))
   (svg-text svg &amp;quot;Wire&amp;quot;
             :x (+ image-width 10) :y (* 2 text-height)
             :fill &amp;quot;#C3E88D&amp;quot;)
   (svg-text svg &amp;quot;The Ideal Copy&amp;quot; :x (+ image-width 10) :y (* 3 text-height)
             :fill &amp;quot;#FF5370&amp;quot;)
   (with-temp-buffer
     (svg-insert-image svg)
     (buffer-string))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The code is fairly self-explanatory. While there may be an even simpler way (please lemme know), I used a temporary buffer to embed the svg in the propertized text prior to feeding to the handy &lt;code&gt;message&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;…and with that, we get a richer display of the current track.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/spiffing-up-those-echo-messages/svg-ready-player-colors-image.gif" /&gt;&lt;/p&gt;
&lt;p&gt;While I haven't experimented with other ways of creating multi-column layouts in Emacs (including images), &lt;a href="https://indieweb.social/@xenodium"&gt;I'd love to know&lt;/a&gt; if there's anything else available besides svg.&lt;/p&gt;
&lt;h2&gt;Enjoying these tips? Using one of my Emacs packages?&lt;/h2&gt;
&lt;p&gt;Help make them sustainable. Consider &lt;a href="https://github.com/sponsors/xenodium"&gt;supporting&lt;/a&gt; this work.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Wed, 11 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/spiffing-up-those-echo-messages</guid></item><item><title>Tiny Great Languages: MOUSE</title><link>https://zserge.com/posts/langs-mouse/</link><description>This is part 3 from series &amp;ldquo;Tiny Great Languages&amp;rdquo;.
Final code is on Github. Part 1: Assembly. Part 2: BASIC. Part 3: Forth/MOUSE. Part 4: Lisp. Part 5: APL/K. Part 6: PL/0. Let&amp;rsquo;s go Forth. A concatenative language available on early computers, a great example of how small, elegant languages can be both powerful and efficient.
I&amp;rsquo;ve already covered building a proper Forth from the ground up in an earlier post.</description><author>zserge's blog</author><pubDate>Wed, 11 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zserge.com/posts/langs-mouse/</guid></item><item><title>Profitable Plant Propagation Prevents Poaching</title><link>https://taylor.town/oh-poaching</link><description>We tried telling people to stop poaching, but that didn't work.</description><author>taylor.town</author><pubDate>Wed, 11 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/oh-poaching</guid></item><item><title>First-Class Helper Functions</title><link>https://lambdaland.org/posts/2024-09-11_parameterized_decisions/</link><description>&lt;p&gt;We&amp;rsquo;re going to be writing a &lt;a href="https://en.wikipedia.org/wiki/Brainfuck"&gt;BF&lt;/a&gt; compiler for a class I&amp;rsquo;m in. Last night I threw together a little interpreter for the program in about an hour; it doesn&amp;rsquo;t do input—that should be easy to add—but it&amp;rsquo;s enough to handle &lt;a href="https://github.com/cwfitzgerald/brainfuck-benchmark"&gt;some benchmarks&lt;/a&gt; for the language, albeit slowly. You can see my repository &lt;a href="https://codeberg.org/ashton314/brainfreeze"&gt;on Codeberg&lt;/a&gt; for the source code.&lt;/p&gt;
&lt;p&gt;I needed one function to do two closely related jobs—the logic was identical, but some parameters needed to change. Fortunately, first-class functions in your language make it trivial to parameterize your programs in elegant ways.&lt;/p&gt;
&lt;p&gt;For those unfamiliar, a BF program looks like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;++++++++++&lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&amp;gt;+++++++&amp;gt;++++++++++&amp;gt;+++&amp;gt;+&amp;lt;&amp;lt;&amp;lt;&amp;lt;-&lt;span style="color: #eceff4;"&gt;]&lt;/span&gt;&amp;gt;++.&amp;gt;+.+++++++..+++.&amp;gt;++.&amp;lt;&amp;lt;+++++++++++++++.&amp;gt;.+++.------.--------.&amp;gt;+.&amp;gt;.&lt;span style="color: #bf616a;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That program prints &lt;code&gt;Hello, World!&lt;/code&gt;. Here&amp;rsquo;s the spec for the language, taken from &lt;a href="https://github.com/sunjay/brainfuck/blob/master/brainfuck.md"&gt;this repo&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;&lt;/code&gt;  move the pointer right&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;&lt;/code&gt;  move the pointer left&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+&lt;/code&gt;  increment the current cell&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-&lt;/code&gt;  decrement the current cell&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.&lt;/code&gt;  output the value of the current cell&lt;/li&gt;
&lt;li&gt;&lt;code&gt;,&lt;/code&gt;  replace the value of the current cell with input&lt;/li&gt;
&lt;li&gt;&lt;code&gt;[&lt;/code&gt;  jump to the matching &lt;code&gt;]&lt;/code&gt; instruction if the current value is zero&lt;/li&gt;
&lt;li&gt;&lt;code&gt;]&lt;/code&gt;  jump to the matching &lt;code&gt;[&lt;/code&gt; instruction if the current value is &lt;strong&gt;not&lt;/strong&gt; zero&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Basically, BF is a little Turing machine: you have a big array of memory and a pointer into that array. The commands move the pointer around and can set or check the value pointed at.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;[&lt;/code&gt; and &lt;code&gt;]&lt;/code&gt; characters form loops and need to be balanced. In my interpreter I wanted to run a preprocessing step so that when I encountered a &lt;code&gt;[&lt;/code&gt; or a &lt;code&gt;]&lt;/code&gt; I would know how far to jump instead of having to search the program for the matching bracket. Here&amp;rsquo;s the top-level function to modify the program vector to replace &lt;code&gt;[&lt;/code&gt; and &lt;code&gt;]&lt;/code&gt; with a struct containing how far to jump:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; jmp &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;amount&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; jmp-forward jmp &lt;span style="color: #eceff4;"&gt;()&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;  &lt;span style="color: #616e87; font-style: italic;"&gt;; replaces a [ command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;struct&lt;/span&gt; jmp-backward jmp &lt;span style="color: #eceff4;"&gt;()&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;#:transparent&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #616e87; font-style: italic;"&gt;; replaces a ] command&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;preprocess-loops! prog&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;for&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;i &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;in-range&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-length&lt;/span&gt; prog&lt;span style="color: #eceff4;"&gt;))])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;match&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-ref&lt;/span&gt; prog i&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;#\[&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-set!&lt;/span&gt; prog i &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;find-matching prog i &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;close&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;#\]&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-set!&lt;/span&gt; prog i &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;find-matching prog i &lt;span style="color: #b48ead;"&gt;-1&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;open&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;_&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;42&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;])))&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;That &lt;code&gt;find-matching&lt;/code&gt; function was a little tricky: in the case of searching for a &lt;code&gt;]&lt;/code&gt; it would have to walk &lt;em&gt;forward&lt;/em&gt; looking for a &lt;code&gt;]&lt;/code&gt; character or a &lt;code&gt;(jmp-backward …)&lt;/code&gt; struct, and it would have to walk &lt;em&gt;backward&lt;/em&gt; looking for a &lt;code&gt;[&lt;/code&gt; character or &lt;code&gt;(jmp-forward …)&lt;/code&gt; struct. The naïve way to do this would be to have a bunch of &lt;code&gt;if&lt;/code&gt; expressions dispatching on the &lt;code&gt;'close&lt;/code&gt; or &lt;code&gt;'open&lt;/code&gt; passed to &lt;code&gt;find-matching&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;find-matching prog start offset kind &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;stack &lt;span style="color: #b48ead;"&gt;0&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let*&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([&lt;/span&gt;addr &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; start offset&lt;span style="color: #eceff4;"&gt;)]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;current-instr &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-ref&lt;/span&gt; prog addr&lt;span style="color: #eceff4;"&gt;)])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;eq?&lt;/span&gt; kind &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;close&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;or&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;eqv?&lt;/span&gt; current-instr &lt;span style="color: #a3be8c;"&gt;#\]&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;jmp-backward? current-instr&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;            &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;zero?&lt;/span&gt; stack&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;jmp-forward offset&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;find-matching prog start &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; start&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; kind &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;-&lt;/span&gt; stack &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;  &lt;span style="color: #616e87; font-style: italic;"&gt;; this isn't our close ]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;            &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;or&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;eqv?&lt;/span&gt; current-instr &lt;span style="color: #a3be8c;"&gt;#\[&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;jmp-forward? current-instr&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;find-matching prog start &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; start&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; kind &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; stack &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;   &lt;span style="color: #616e87; font-style: italic;"&gt;; deepen stack because we found another [&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;find-matching prog start &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; start&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; kind stack&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;or&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;eqv?&lt;/span&gt; current-instr &lt;span style="color: #a3be8c;"&gt;#\[&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;jmp-forward? current-instr&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;            &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;zero?&lt;/span&gt; stack&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                &lt;span style="color: #81a1c1; font-weight: bold;"&gt;...&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;            &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;or&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;eqv?&lt;/span&gt; current-instr &lt;span style="color: #a3be8c;"&gt;#\]&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;jmp-backward? current-instr&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                &lt;span style="color: #81a1c1; font-weight: bold;"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                &lt;span style="color: #81a1c1; font-weight: bold;"&gt;...&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))))&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The need for a stack to find matching delimiters should be pretty obvious: if I have an open bracket &lt;code&gt;[&lt;/code&gt; and am looking for the matching close bracket &lt;code&gt;]&lt;/code&gt;, then I need to make sure I don&amp;rsquo;t get confused by other open-close pairs in between.&lt;/p&gt;
&lt;p&gt;The function above is pretty clunky: all the logic for checking if the stack is empty gets duplicated, and the logic for adding/decrementing the stack is a mirror image between the two branches of &lt;code&gt;if (eq? kind 'close)&lt;/code&gt;. There might be a few ways to rearrange this example so that it&amp;rsquo;s a little better, but there&amp;rsquo;s one big change we can make.&lt;/p&gt;
&lt;p&gt;The key insight is this: we have first-class functions, so why not parameterize the condition we use to check the current command to know if we should push the stack, pop the stack, or return a new struct? Here&amp;rsquo;s how we do it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-racket"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;find-matching prog start offset kind &lt;span style="color: #eceff4;"&gt;[&lt;/span&gt;stack &lt;span style="color: #b48ead;"&gt;0&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;close? x&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;or&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;jmp-backward? x&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;eqv?&lt;/span&gt; x &lt;span style="color: #a3be8c;"&gt;#\]&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;open? x&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;or&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;jmp-forward? x&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;eqv?&lt;/span&gt; x &lt;span style="color: #a3be8c;"&gt;#\[&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;define&lt;/span&gt; addr &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; start offset&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;let-values&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;([(&lt;/span&gt;needle-pred other-pred bump jmp-maker&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;eq?&lt;/span&gt; kind &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;close&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                    &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;values&lt;/span&gt; close? open? &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt; jmp-forward&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                    &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;values&lt;/span&gt; open? close? &lt;span style="color: #b48ead;"&gt;-1&lt;/span&gt; jmp-backward&lt;span style="color: #eceff4;"&gt;))])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;needle-pred &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-ref&lt;/span&gt; prog addr&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;zero?&lt;/span&gt; stack&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;            &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;jmp-maker offset&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;            &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;find-matching prog start &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; bump offset&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; kind &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;-&lt;/span&gt; stack &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;        &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;other-pred &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;vector-ref&lt;/span&gt; prog addr&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;            &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;find-matching prog start &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; bump offset&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; kind &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; stack &lt;span style="color: #b48ead;"&gt;1&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;            &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;find-matching prog start &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;+&lt;/span&gt; bump offset&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; kind stack&lt;span style="color: #eceff4;"&gt;)))))&lt;/span&gt;&lt;span style="color: #bf616a;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now we have &lt;code&gt;needle-pred&lt;/code&gt;, which returns true if the current command is the current thing we&amp;rsquo;re looking for; &lt;code&gt;other-pred&lt;/code&gt;, which tells us if we need to increase the stack; &lt;code&gt;bump&lt;/code&gt;, which just tells us which way to move the offset; and &lt;code&gt;jmp-maker&lt;/code&gt; which we use to build the right kind of struct to return when we&amp;rsquo;ve found the matching delimiter and the stack is empty.&lt;/p&gt;
&lt;p&gt;I really like that &lt;code&gt;let-values&lt;/code&gt; lets me bind a bunch of variables on a single condition; other languages can do something similar if they have e.g. tuples and rich pattern matching.&lt;/p&gt;
&lt;p&gt;First-class functions are a powerful way to parameterize your code, and it doesn&amp;rsquo;t just have to be with higher-order functions. Clearly, generic functions like &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;filter&lt;/code&gt; would be basically useless without the ability to take functions as parameters. But you can also tailor the behavior of your program by pushing the differences between two or more possible scenarios into functions, and then select the proper set of functions in one conditional.&lt;/p&gt;</description><author>Ashton Wiersdorf on Lambda Land</author><pubDate>Wed, 11 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2024-09-11_parameterized_decisions/</guid></item><item><title>Apple M0110 Keyboard CPU replacement - part #1</title><link>https://mikewarot.blogspot.com/2024/09/apple-m0110-keyboard-cpu-replacement.html</link><description>&lt;p&gt;My friend Jim bought an Apple Macintosh at the Vintage Computer Festival Midwest on Saturday. It's got a bad keyboard. The keyboard has a bad Intel 8021 CPU.&lt;br /&gt;&lt;br /&gt;I need to replace that chip. It's going to take wayyyy more time that I'm used to dumping into a project successfully.&amp;nbsp; This is part of my process to push myself forward.&lt;/p&gt;&lt;p&gt;So far I've:&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style="text-align: left;"&gt;&lt;li&gt;Searched for an 8048 series disassembler, found none&lt;/li&gt;&lt;li&gt;Downloaded the &lt;a href="http://bitsavers.informatik.uni-stuttgart.de/components/intel/8048/1980_MCS-48_Users_Manual.pdf"&gt;8048 series manual&lt;/a&gt; from BitSavers&lt;/li&gt;&lt;li&gt;Decided to write an 8021 specific disassembler in pascal&lt;/li&gt;&lt;li&gt;Started a Lazarus project in my C:\res21 folder&lt;/li&gt;&lt;li&gt;Published the above to an &lt;a href="https://github.com/mikewarot/res21" target="_blank"&gt;MIT licensed GitHub repository&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Let's see how this goes.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;</description><author>--Mike--</author><pubDate>Tue, 10 Sep 2024 23:02:49 GMT</pubDate><guid isPermaLink="true">https://mikewarot.blogspot.com/2024/09/apple-m0110-keyboard-cpu-replacement.html</guid></item><item><title>Git: Multirepo patch set</title><link>https://blog.gnoack.org/post/git-multirepo-patch-set</link><description>&lt;p&gt;In the context of &lt;a href="https://lwn.net/Articles/989215/"&gt;Alejandro Colomar stepping down as a man-pages maintainer&lt;/a&gt; :(, &lt;a href="https://lwn.net/Articles/989398/"&gt;I learned from him&lt;/a&gt; that
it is possible with &lt;code&gt;git&lt;/code&gt; to create an email patch set that spans multiple target repositories.
Specifically, he &lt;a href="https://lore.kernel.org/linux-man/CAEf4BzZzE94QUdhWPmrMzRBRLa=nm86Mdm5vow688jKq3HzJeA@mail.gmail.com/T/#t"&gt;pointed to a review thread by Jiri Olsa&lt;/a&gt; where this was done, and whose outline takes a similar shape to this (simplified):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[PATCH proj v3 0/3]&lt;/strong&gt; foobar: Add transmogrifier
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;[PATCH proj v3 1/3]&lt;/strong&gt; foobar: Prepare flux compensator&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[PATCH proj v3 2/3]&lt;/strong&gt; foobar: Add transmogrifier feature&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;[PATCH man v3 3/3]&lt;/strong&gt; foobar.1: Document transmogrification&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Other than in normal review threads, &lt;em&gt;every mail subject is prefixed with an additional tag here&lt;/em&gt;, to indicate which project it belongs to.&lt;/p&gt;
&lt;p&gt;It was not obvious to me how to best achieve it with the existing tools, so I attempted to reconstruct how he had done it, and ended up with the workflow that I will describe below.
&lt;a href="https://lore.kernel.org/all/Zt__OKtOj8AZGy4X@krava/"&gt;This is not the same workflow that Jiri Olsa has been using&lt;/a&gt; after all, but it&amp;rsquo;s close, and unusual enough that it&amp;rsquo;s maybe worth documenting here.&lt;/p&gt;
&lt;h2 id="1-pull-both-original-repositories-in-the-same-local-repository"&gt;1. Pull both original repositories in the same local repository&lt;/h2&gt;
&lt;p&gt;We first make sure that all necessary commits are stored in the same local repository, by pulling in a second remote repository with &lt;code&gt;git fetch&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;This is unusual, but it is possible.  As a result, your local repository
contains two unrelated commit histories, for instance one for the linux kernel
and one for the man pages:&lt;/p&gt;
&lt;div class="pikchr" id="pikchr-0"&gt;
&lt;div class="pikchr-svg"&gt;
&lt;svg viewBox="0 0 670.32 211.68" xmlns="http://www.w3.org/2000/svg"&gt;
&lt;path d="M155.16,189.36L515.16,189.36A7.2 7.2 0 0 0 522.36 182.16L522.36,9.36A7.2 7.2 0 0 0 515.16 2.16L155.16,2.16A7.2 7.2 0 0 0 147.96 9.36L147.96,182.16A7.2 7.2 0 0 0 155.16 189.36Z"&gt;
&lt;circle cx="189.051" cy="72.0512" r="14.4"&gt;
&lt;path d="M203.451,72.0512L228.651,72.0512"&gt;
&lt;path d="M228.651,72.0512L253.851,72.0512"&gt;
&lt;path d="M253.851,72.0512L279.051,72.0512"&gt;
&lt;circle cx="293.451" cy="72.0512" r="14.4"&gt;
&lt;path d="M293.451,57.6512L293.451,28.8512"&gt;
&lt;path d="M212.451,39.6512L293.451,39.6512L293.451,18.0512L212.451,18.0512Z"&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="252.951" y="28.8512"&gt;linux-base&lt;/text&gt;
&lt;path d="M307.851,72.0512L333.051,72.0512"&gt;
&lt;circle cx="347.451" cy="72.0512" r="14.4"&gt;
&lt;path d="M361.851,72.0512L387.051,72.0512"&gt;
&lt;circle cx="401.451" cy="72.0512" r="14.4"&gt;
&lt;path d="M401.451,57.6512L401.451,28.8512"&gt;
&lt;path d="M401.451,39.6512L498.651,39.6512L498.651,18.0512L401.451,18.0512Z"&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="450.051" y="28.8512"&gt;linux-feature&lt;/text&gt;
&lt;circle cx="189.051" cy="158.451" r="14.4"&gt;
&lt;path d="M203.451,158.451L228.651,158.451"&gt;
&lt;path d="M228.651,158.451L253.851,158.451"&gt;
&lt;path d="M253.851,158.451L279.051,158.451"&gt;
&lt;circle cx="293.451" cy="158.451" r="14.4"&gt;
&lt;path d="M293.451,144.051L293.451,115.251"&gt;
&lt;path d="M217.851,126.051L293.451,126.051L293.451,104.451L217.851,104.451Z"&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="255.651" y="115.251"&gt;man-base&lt;/text&gt;
&lt;path d="M307.851,158.451L333.051,158.451"&gt;
&lt;circle cx="347.451" cy="158.451" r="14.4"&gt;
&lt;path d="M347.451,144.051L347.451,115.251"&gt;
&lt;path d="M347.451,126.051L439.251,126.051L439.251,104.451L347.451,104.451Z"&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="393.351" y="115.251"&gt;man-feature&lt;/text&gt;
&lt;path d="M522.36,72.0512L560.16,72.0512"&gt;
&lt;path d="M560.16,46.8512L560.16,97.2512A54 10.8 0 0 0 668.16 97.2512L668.16,46.8512A54 10.8 0 0 0 560.16 46.8512A54 10.8 0 0 0 668.16 46.8512"&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="614.16" y="70.0712"&gt;linux&lt;/text&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="614.16" y="90.2312"&gt;origin&lt;/text&gt;
&lt;path d="M522.36,158.451L560.16,158.451"&gt;
&lt;path d="M560.16,133.251L560.16,183.651A54 10.8 0 0 0 668.16 183.651L668.16,133.251A54 10.8 0 0 0 560.16 133.251A54 10.8 0 0 0 668.16 133.251"&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="614.16" y="156.471"&gt;man-pages&lt;/text&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="614.16" y="176.631"&gt;origin&lt;/text&gt;
&lt;polygon points="147.96,72.0512 136.44,76.3712 136.44,67.7312"&gt;
&lt;path d="M142.2,72.0512L110.16,72.0512"&gt;
&lt;path d="M2.16,97.2512L110.16,97.2512L110.16,46.8512L2.16,46.8512Z"&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="56.16" y="61.9712"&gt;linux&lt;/text&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="56.16" y="82.1312"&gt;worktree&lt;/text&gt;
&lt;polygon points="147.96,158.451 136.44,162.771 136.44,154.131"&gt;
&lt;path d="M142.2,158.451L110.16,158.451"&gt;
&lt;path d="M2.16,183.651L110.16,183.651L110.16,133.251L2.16,133.251Z"&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="56.16" y="148.371"&gt;man-pages&lt;/text&gt;
&lt;text fill="rgb(0,0,0)" text-anchor="middle" x="56.16" y="168.531"&gt;worktree&lt;/text&gt;
&lt;text fill="rgb(0,0,0)" font-style="italic" text-anchor="middle" x="335.16" y="199.44"&gt;local repository&lt;/text&gt;
&lt;/svg&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;You can simplify your life here a bit by using &lt;a href="https://git-scm.com/docs/git-worktree"&gt;git worktrees&lt;/a&gt; &amp;ndash; with these, your can keep doing your Linux work in a separate directory to your man page work, but still keep all of the changes in the same repository.&lt;/p&gt;
&lt;p&gt;In the example above, we have prepared two commits in the &lt;em&gt;linux-feature&lt;/em&gt; branch, based on the &lt;em&gt;linux-base&lt;/em&gt; branch from upstream, as well as one commit in the &lt;em&gt;man-feature&lt;/em&gt; branch, based on &lt;em&gt;man-base&lt;/em&gt;.&lt;/p&gt;
&lt;h3 id="example"&gt;Example&lt;/h3&gt;
&lt;p&gt;Starting from two repositories &lt;code&gt;/tmp/git/linux-origin&lt;/code&gt; and &lt;code&gt;/tmp/git/man-origin&lt;/code&gt;, let&amp;rsquo;s fetch both repositories into the same local repository and create a worktree for each:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;gnoack:/tmp/git$ git clone /tmp/git/linux-origin linux
Cloning into 'linux'...
done.
gnoack:/tmp/git$ cd linux
gnoack:/tmp/git/linux(main)$ git tag linux-base
gnoack:/tmp/git/linux(main)$ git remote add man-origin /tmp/git/man-origin
gnoack:/tmp/git/linux(main)$ git fetch man-origin
remote: Enumerating objects: 12, done.
remote: Counting objects: 100% (12/12), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 12 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Unpacking objects: 100% (12/12), 948 bytes | 948.00 KiB/s, done.
From /tmp/git/man-origin
 * [new branch]      main       -&amp;gt; man-origin/main
gnoack:/tmp/git/linux(main)$ git worktree add /tmp/git/man man-origin/main
Preparing worktree (detached HEAD efbfa45)
HEAD is now at efbfa45 4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We now have a worktree for the man pages at &lt;code&gt;/tmp/git/man&lt;/code&gt; and one for Linux at &lt;code&gt;/tmp/git/linux&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We now prepare the Linux and man page commits on top of these git repositories in the usual way, and make sure that we create branches for the pristine states called &lt;code&gt;man-base&lt;/code&gt; and &lt;code&gt;linux-base&lt;/code&gt;, and call our work branches &lt;code&gt;man-feature&lt;/code&gt; and &lt;code&gt;linux-feature&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="2-format-a-patch-set-that-spans-two-otherwise-unrelated-branches"&gt;2. Format a patch set that spans two otherwise unrelated branches&lt;/h2&gt;
&lt;p&gt;Create a giant patch set that spans two branches.
Indicating the desired commit range by using dotted range notation &lt;em&gt;twice&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;This invocation is compatible with common flags like &lt;code&gt;-v3&lt;/code&gt; for the patch set version and &lt;code&gt;--cover-letter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We use &lt;code&gt;--subject-prefix&lt;/code&gt; so that the mail subject is additionally qualified with an abbreviation for the main project.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git format-patch -v3 --cover-letter \
   --subject-prefix=&amp;quot;PATCH proj&amp;quot;    \
   linux-base..linux-feature        \
   man-base..man-feature
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Remember from the &lt;a href="https://man.gnoack.org/7/gitrevisions"&gt;&lt;em&gt;gitrevisions&lt;/em&gt;(7)&lt;/a&gt; man page that a &lt;em&gt;revision range&lt;/em&gt; in Git parlance is a set of commits, which are not necessarily ordered in a single linear chain.  (Somewhat surprisingly, they are also not all connected here, as you would think from reading the man page.)&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git format-patch&lt;/code&gt; includes some additional features for controlling the ordering, but in practice it seems that commits are ordered by date if they are not connected.&lt;/p&gt;
&lt;h2 id="3-final-hand-editing"&gt;3. Final hand-editing&lt;/h2&gt;
&lt;p&gt;Hand-edit the subject prefix in the man page commit&amp;rsquo;s email, so that the man page commit stands out among the others for the reviewers.&lt;/p&gt;
&lt;p&gt;And sure enough, after &lt;code&gt;git send-email&lt;/code&gt;, the mail thread has the expected form (in &lt;a href="http://mutt.org/"&gt;&lt;code&gt;mutt&lt;/code&gt;&lt;/a&gt;):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; [PATCH proj v3 0/3] foobar: Add transmogrifier
 ├─&amp;gt;[PATCH proj v3 1/3] foobar: Prepare flux compensator
 ├─&amp;gt;[PATCH proj v3 2/3] foobar: Add transmogrifier feature
 └─&amp;gt;[PATCH man v3 3/3] foobar.1: Document transmogrification
&lt;/code&gt;&lt;/pre&gt;</description><author>Blog on blog.gnoack.org</author><pubDate>Tue, 10 Sep 2024 22:03:55 GMT</pubDate><guid isPermaLink="true">https://blog.gnoack.org/post/git-multirepo-patch-set</guid></item><item><title>Practicing with Polylines</title><link>https://josh.works/polyline-practice-again</link><description>&lt;p&gt;This is a first pass at trying to do something interesting (repeatedly) with the same base primative, in this case, a “polyline”. Read the rest of this post, understand what we’re going for, then go to &lt;a href="/polyline-practice-again-strava-auth"&gt;part 2: get your own polyline from strava&lt;/a&gt;. It’s not trivial to get, but its interesting data, and you’ll have an abundance of polylines, if you want them.&lt;/p&gt;

&lt;p&gt;The polyline in question I got from Strava, after recording a scooter ride:&lt;/p&gt;

&lt;p&gt;This post should be interesting to programmers and non-programmers alike. A polyline is a way of encoding a bunch of latitude/longitude pairs, so it can be drawn in detail on a map.&lt;/p&gt;

&lt;p&gt;Here’s what the polyline looks like, raw:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;oops, in a browser this string simply disappears off the side of the page. Here’s what it looks like in an editor:&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;img alt="polyline" src="/images/polyline_preview.jpg" /&gt;&lt;/p&gt;

&lt;pre&gt;
u}pqFttt_S@NFNB?JA@@DIH@@CEEBAPAFBHK@@FEf@??FLEPA\?LD@CECDI?BBEH?DHLCT@BLBtB?@FADDDMADC@BJEIBAEMA@AC?HKACDDAHQEMA{@?e@BCR@FBJ?DBJAHB`@AF@LAHE~@?FEJLVC\?DNBA@BFBBAPMJAP@NCBE?CIO@Cn@Fl@Cj@BDEDBFALDN?^?HILCRHNAVD~@?XKz@k@HSBWHKVOD?b@VJBHA?KCQBi@n@Sp@e@`AMHIJDZB^?\KPKVYLq@f@i@Nm@L}@Cg@Q_AMWDH?CUa@g@YCEBGJQ`@]Eu@AA?HAG?f@CFSZITDHXXJPN`@BBAU}@s@_A][EEBAA?OEA}@JQL@F?EMAKLKBEFPd@Ma@QBA?D@EHKBKJKDILEGHe@@WAAEA@BBCADD@ABCK@UCW@IRO\GDGLABBF?TGD@TI^GX?@EDBJCNBFCLDHJLMBBBCL@NFDC@MH?JN@X@BD?NIJBh@SZ@HK@BVA@CDALDHHRAHBZBFEBYFO?MBSAECHDPANOIOC?CJIFDFB@DDEEA?DC?B@ALLKADACADC@AC@@GBAA?ECBARDPBKEHDA?IEA@@?C@F@AB?@FCKAF@D?C@@EC@A?BCC?E@P?CCB?GFAGB@CAA?BC?D??D@K?AAB@@AAC@@EDACD?CBBABCA@CADACB@CAC_@@UCKAABGAUAEICAEBa@FU@_@@ED?^BDA@IEQ?k@FCF?DBBGEOCWCAMAG]EAKBADGECECa@Bk@Dc@EaBDQE?CGD_@PIH@LHD?@BIFEEOAf@CDAAI@DBEF@IC?B?CFFDBBFF@LC@ICEQE?B?M@LCAD?EDAC@A@DA@FKACGCFBABCAEIHDB??DCABB@AEGDFEABFAC?CABECJHGBDXXPD`@L@HBDZ@NAJC@O?DOD@CM@BBABNAHEBI?GEKAM?QDDCL?RBJ?HIBKISR@CHBACBECDDG@BB?C@FCGKrC@\?BCABB@\?d@@@?C@H@AC?CF?\GFK?G@EA?CG?gABQFCFCn@Ft@JDT?BFBKNBEGGE?EGAMD@EJQBBFIJGB?@FAFC@B@ABHQ@BMN?H?KCA@@@A@@ABAAHEA?@B@GDCI@?BAEABCA?CB?CBBHGCB?CCBFC?@E?@BC?BCD@CC?BAA@?E@?@AC@AF@??C@G@LEEWFD??FHALD@DGF]CMR@SCAABDADDC@AEED@ADABDCBEA?@?CDAEB?NCCBEABBBDPXJFFDGJ?RGBEB@\R^LHJJ@d@Kb@E^MD?@@C@@IABDEDBj@?~@GNBJANDr@?TDXNNPPd@Fz@Gz@?f@BZAVBb@Cf@@HELG@?EDB@ADHBrBDz@KpA?~@Kv@a@|@_@Va@`@G?KES[KGKWGEa@CQEBDk@CYBKDc@XSAODUA]DSC[Bk@Ck@BUAUDIAAAUFQIYCIMK]MAODEDAGDBC?CIBUECE@DEACCFD@ACADE@AEFFGMFA?@IAI@AGBMCE@HBBKBG?DCDKLCG@@IBA@MZWA@?CGGCAAGKUAK@MCKIGM_@IA@KDEC?CHAAAD@BB?CA?B@CHJADBBCCC@CNGFBHJJMCC@DT?F?AF@FH?HGXBTFNKPFAAAG@CD?E@DH@@EAIDD?ACCFDDLGB?FELAAC?CMA?BJBMEKA?@A?DJHE?CCCFCAJED@ACA@@EHXFC?EEACKFQ?EEAM@AFC@L?LUHXD?ABCCCFAC@B@GKG?KOUAKKIq@RCD@DHB?@CBVUPo@D?A?BDNQCS@GDQHMCHDIJq@HMBMDI@KC??DBCH?EO?CBACKL_@HI?KCXICJMFS?GGCID@PPQA@GEE?HDHKHC^DBABGLENA@CFe@CMCk@CACD@BCAEIBMB@BGHCB@EM?KBI\CNBPYEGGB?MCCCLG?@CCHG@EE@CFA?DSFAJBEEDBXCF?V??@E@MFCOi@?KDCB@?FIB?BB`@CN?ADDHY@a@AAADEBAD@HFEC?@CEA@EB@AG@BPEJ@CGOGQ\AIDCA@EYBMFKH?E^BXAHAAGQFB@ACC?HHIDKC?@BDGB?@FGDE?CKCDAG?FHUYEIDBRHBDF?C?DAGBO?KD[KUEBIABDE@WAIBF?M?ADDv@D@FE?FHGJBBH?RDD@MGOGG?IB@C@@ABF@LHLJBFABKCIBJE@@BG@IJO@?BB?CDB\DVCDCME?@L@FAB?IFL@J?NEL@X?CCPCAADKAEBWOIBO?a@OG?OQQGG@ML@LEJCZ@@AD@AGJI`@EFCABA@BCH?CMJOBENIp@CDAHEDAAIDMH@@I@?B@AJF@PEFQDC?@@D?WHYRo@R?DQDKRQJG?UFIAm@HOEACDBABE??FIE@AJABKOJGNKACFFdBADG@@BEAZAp@KdCCRG@?AB@CDHLA^Dv@Gd@BNEDUJUBQDID?Ae@C?IBECCC@FGPDTKBIKVy@?UFQF]GMKK[QMAOFYVE?OHO@ULSRGBSb@^r@RXJDXD^ALBhAEDWCI?SC?EBCJ?JDEE@B@BCACC??BDEB@C@FC@CM@GFAABBNKFK@@G?KBIL@@XMDIGH@@BA?OMGMFGFYh@CNAAAEADIDIGSA[HUCGEQ_@_@Y_@q@c@NEDUD]LI?MC}@@e@GUAGDEXUXg@Xa@NGFc@AOBY?k@Ig@Fq@A]DCDc@AY@GGQD_@@GLC@eA@OCqCG}@HO?ICc@DYGMBEEM?MLSE_@?SDm@EO?IDGEG?KFFl@?n@K@QC]@OAKFAFFh@I\?HDBTCBCEAE@@F@B?Da@PJEKAACDAVWd@[??ALBFNIWHDA@FFA?GGABICC?@s@FSAj@@J@BFCEQCs@DLGH?GBF?C@F?EAB?C?BACAC@?CCBBDHBFAOABA?ACASDH?BBFA@CBBH?KGM@HAMABDF@ICB?EEDDC@?AFAGAB?AAC?D?AB@AO@JDcCP|BWF?DFCGAB@EDLEKFD?@IGF?ED?CAAFHAEA@ACCFECC@?CIDBEGGABBBJAH@@CSDREBMGJB@CAB@AAABBBCGABB??BCBUJFKB@C?CCTIYDEFFEZAICWJAKD@HEF@AB?EDDAHHKCGOJ?BHCENNM@EA?AAD?C@?C?D?E?B@AA??A?DH??BKC@C@DCE@D?EBBCCM@F?AAFBAA@?C@@BA?BCA?@??CA@@AA?@@?A?@@AA?@?CA@BGEI@ABR?KHB?ACF?CIFWIFADB?JEKHDDECDE@HIDFA?MABD?GNEDNQCC@@GDE?BAFA?BD?MBCBBAAAFKA@@FAA@C@BCBI@DEBAABB?@AAE?BC?@EB@GADD?@AA@BGSFRAA@A?H@AAECJEFFE@M?H@AAI@BDAG?@@AC?@CABB?A@?A@@EC??CC?F?CJB?CACB@?E?AGFECBEEHB@FABACJ@BCC@KKC@BGF@A@ACDFAF@DAI?@E@DGDA?@CBCAADBG?BAAAFEAF@AAEBB@?EC@P@BBI@EKBKC?AHBFCK?DBBHAGAADO@H??BD?C?@ADBCGD@YJXAIKBA?EGFBG?B@CA@AC?BC@CCDC?ADAEJB@ACC@B@CBN?EC?BGD?FBEI@B?@IM@G?NIH?DDIIHD?EAD?ICBIAHACDBBA?H@EBLAO?BAG?BCC@B@GED?ABFB?D@CEADEM?AABCDDKAL?CBBA@FEC?CI?B?A??EB@EALBABBC?BE@DAA?@@A@BAD?E@D?WDF?FE?BEAFBCABAA@ECDBCEEDB?ACD?ABDAIBD@AABAC?BAI@F?AGC??@DAC@DDA@BCCBE?AIH@@J@AADB@?KE?DFC?EEKCD?ADD@AA@AD?E@BAGCL?G?A@@ABBE?@EIAL@AB@BAG@@CAF@O?@@C?@@DA?CC@B?C?@A@?CB?EB?CAD?SAh@FK?MCMBF@?@B??EBCD?OABCNBKB?BH??CL@Q?@E@FC@HBI?AAE?HAAAGBFB?DB@GCBCEAH@G?B@CBDCA?BAAA??G@FA?CC@BAGAIBT@QBl@GKAO@B?A?F@UBD@BAI?FMCNBOEHDEKDJIMDFBWDTBDC@GAAB?ECF@EF@GCJBMCF@EAHACF@HCG?F@K?@DE?BMFDCAACUBRACBN@QDDACGBAI?H?C@B@?I[JH?A@NCKCS?P@VE@@@J[MJ?HFEDYAN?@E@@FCL?FCK?QBJ@O@b@@MIM@LDICJE@KA@@@AFG@DFE@@@E?AE@?CCBAI?FJF?E??ICEF?C??HCABC@F@CC??CE@FAAE?D@AC?@@F?G@@DC?AE@@?@@AGAH@A??C@?A?CBFEIB@?CED@AECHB?C?FCCAB?AB?CA?@@ADCARAG?BBIAD@A?DEIFFCG@BEB?E@?AEHDE?ECB?DBAG?DAA@B@@AEA?C@BEDD?@E?BBAAC?BC?AED@GDDE?BBC?BGCLGM@A?FACHEEBAE?D@A@D?A@?CD?M@F?A@HBCA@GCB?CC@D@C?@@A?@B?ADCICCDD@@CBHOAJAAAB?@EG@FNAA?EEAA@BCC?FACEB@E@BAAE?FCBJEC@B@SBLD?CEADDDCI?BA?CCA@??B@CB??BCB@AAAB@CAB?C@@CB@C@B?I?@?@BGAJIE?DEEAHFE@A?BCE@J?CAD?A?@AG?@@ECHDOB?BHDCA?G@?A@FAG@?@D@KJDEACBABC@@C?@BEAD@?@CABB@CK?HCKDB?A@BEB??@CABAAAEDHAGADA?CC@@@C?EB@?AABB@C@@C?BAAAJ@C?EADAEAA?F@EAD?IDDAEAD@A?@?@AE@BA?@C@B@ABEABCCADACBG?J?B?CA@?C?ACD@BAC@@ACA@BC?ECB@C@D@C@DAEAF?@@A@EA@CB@E?FAIDECE?ABCA@ANDBAEAD@CC@CC?D?E?EDNBC@@B?EEA?@B?G?DAADAA@CHA@@E?ABCABAA?DB?@CC@CBBE@D@CADAEA@EA?@@A@@HI@DIA??ED??BC@@CCABEACGDC?@A@BH@?DC@JBECDCGCBCG@IA@?C@@@BAACD?BCEABABBAHLB?GC@?FQCFA?ARFEBD?OAFACDCA@CG@F@C?CE@A?BD?C@AAH?@CK@D@FAACBA?CEBB@@AQADACDG?L?ACA?DBCFDBABJ?KEEEI@HADB@AACG@B?@@A?BAAANCB@E?AFE@B@E?JCCB@@CA@C@?A?BAAC@?I@?DI@BB@CCA@EEE@@?F@A?B@A?BDA?CA?@AK@B?DIE@BFB?EBBAC?D?BA?ACAB?C@D?@D?ICACBHDGDEAEGG?T@GI@?C@CAABF@DCE?D?A?@@C?D@BA@?A@C?@BE?BBA??@B@ACB@EA@AD@KAPBKE@??@C?DACCB@C??@DBEADDEA@BA@B?G?CCD?C@@CBAGCBAI@?BF@AC@@A@@AEAAEC?DBA??AA@D?BKOAPBM@LBC?D?K@L@C?@B@?CCJ?ICE@FAGAJ@O?@AC?BA@BF@M?AGCCD?OCA@DBNDEAAJ@A@BAEDBIGDA@@E?BDCEAD?AD?CA@ADFAGA?B@QMLFBBA@?AEBACHAG@@??B@C@@C@BB@CEB@EC?DABFEC?@@ACE?BACIAB??BAADCAC?BC@B@?@E@F?@@CEDAA@?CIAJ@A@E@J?K?D?C?PFMAFDJ@ECFA?AQMOH@?AE@BDAA?B@FCADB@C@A@?CBC@@GA@AABBCDDC@B?ACQ?H@?BC@E?PEAABAC?ABCCBECDHFMBBEK@C@@BPGD@Q@AF@@BCLSDAA?@@KBB?CD@AGBJAQ@DCRAI@CE@@GBD??EDAJDCFYCGCCBLCCC@BHAC@B?A@DECBB@CA@AAB@CA@B?AAABBA?@AA?@W@NCC?@?EFa@DFIAA`@??GPCEBBDCACBBAA?@?ACABB@?GCDB@C?@AF?M@BA@D?CC@HCCB@?AC@C?@BAI@DCMCE@BLB??FC@BCCQPGJ?EFMD@HFGCAF@MD?ABCH?S?@DDDBAG@@G@??EC@CAB??BFI@?EDE@@FFS@BCDG?CDCCJD?DCA@CCGBCDBCA?B@ELDCEHBGA?@D?EAEDBCAAC@?BEBMEREB@KDBCF?C@DCC?AE@DDGG?KFGALCB@AB?AB?G?F@ZGKAYHVGB?ODEAAJD@?FB@BCACEC@CABE?@CC?IMFVL??IE??@AA?@?EC?B??D?E?@@A?BCC@FBDAE?DB?GK@??@?AAA?B?C@?@NDCCFBEFEEGM??BD@A@E?FCMCE?J?@BBCI@HCD@I?ABD@?C?BCAF@M?ABBBFAAA@BBAC@?EBBCAIBBKDACA?@E?BBAEEDICNBO??CD?BBGBB@F@CCBCG?B?ADFFBCC@BI@@CCDDI@@ACAAGQFJADBDGB@ECC@PFGDIATCE@ICBCD@AAE?C@@@C?AAHAIILAJDQ@A?FBABQ@?DC?LA@KC@B?@?ACBAE?ABB@KBB?CAPAE?@AGAH@C@?DB?ACFAAEK?J@M?GBD?ADBAABHACEBAFDK@D@O@EC@AECHCJ@C?D?ACDAGFDB?ACCGB@@M?b@?I@DFD@?ED@CCH@Q@EA?A@@C@QESH?IJ?VEDIDCYBTGUJLADDCGBFA??DAEBECGCBHDJ@I?Gn@?YH?CEEBEA?ADAEFE@AAFEB?BFCFGHECEO@GNGCEGBC?FKJD@HDBV@HIKAOFIC?BHBBAI?DCACKGE@?@FBEFAKKMHAK?R?SCNB?BHNADB?BFFBD?c@GHEBEA@QAMBF?\JAEG@@CH?BEMAHIEBCK?BD@GTBDGGJGGBBC@??EFHYAHAA@BBPD?EKE?@ACB@CBBCA?@??CAB@@@AEA@?AB?CBBAC@DEABBC@BAA??GDDAB@GC??@?CCDBEBB?DCI@B?CABAC@@A?@??AAB@C?D?E?B@CA@B@C??A?@BAA??DC@@CC?BE
&lt;/pre&gt;

&lt;p&gt;That string is suuuuper long. The only way to get it on your clipboard is to &lt;em&gt;triple&lt;/em&gt; click it, highlight the whole thing, &lt;code class="language-plaintext highlighter-rouge"&gt;ctrl-c&lt;/code&gt;. You could then paste it into a polyline decoder. I googled my way to this one: 
&lt;a href="https://www.daftlogic.com/projects-convert-encoded-polyline-to-latitude-longitude-list.htm"&gt;https://www.daftlogic.com/projects-convert-encoded-polyline-to-latitude-longitude-list.htm&lt;/a&gt;, and see all 3321 lat long points. Here’s a snippet of what some of it might look like:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;update, using a different polyline than what I started this whole thing off with - it was maybe giving me issues.&lt;/em&gt;&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;39.72873,-105.00070
39.72877,-105.00071
39.72882,-105.00062
39.72894,-105.00062
39.72898,-105.00060
39.72904,-105.00051
39.72904,-105.00040
39.72908,-105.00035
39.72925,-105.00034
39.72929,-105.00030
39.72930,-105.00024
39.72932,-105.00038
39.72933,-105.00039
39.72951,-105.00032
39.72967,-105.00032
39.72971,-105.00027
39.72979,-105.00020
39.72992,-105.00015
39.72999,-105.00005
39.73016,-105.00005
39.73020,-105.00004
39.73031,-105.00007
39.73039,-105.00006
39.73043,-105.00002
39.73043,-104.99993
39.73040,-104.99989
39.73032,-104.99991
39.73033,-104.99996
39.73024,-105.00000
39.73018,-105.00000
39.73016,-104.99998
39.73023,-104.99993
39.73037,-104.99995
39.73037,-104.99992
39.73036,-104.99997
39.73033,-104.99999
39.73032,-105.00005
39.73030,-104.99999
39.73027,-105.00000
39.73029,-105.00001
39.73028,-105.00000
39.73029,-104.99998
39.73032,-104.99993

&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Those lat/long pairs are not super useful to look at, so to make a polyline ‘useful’/viewable, you need a map. (and html and javascript, including one &lt;a href="https://github.com/jieter/Leaflet.encoded"&gt;very specific JS package&lt;/a&gt;, I suppose, and global supply chains of computing technology and &lt;em&gt;the internet&lt;/em&gt;!)&lt;/p&gt;

&lt;p&gt;One can pop the polyline into &lt;a href="https://developers.google.com/maps/documentation/routes/polylinedecoder"&gt;Google’s polyline decoding utility&lt;/a&gt; to see it rendered. Here’s what the original polyline I was working with looks like:&lt;/p&gt;

&lt;p&gt;&lt;img alt="polyline" src="/images/polyline-decoder.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;If you plot the polyline above (triple-click, ctrl-c, paste) you might see activity data from Denver, I went on a multi-hour walk with my kid through the local park and botanic gardens. More about those later.&lt;/p&gt;

&lt;p&gt;If you &lt;em&gt;don’t&lt;/em&gt; see that data, maybe there’s some issues with the copying and pasting.&lt;/p&gt;

&lt;p&gt;Now, lets do something interactive, close to what google is doing there under the hood.&lt;/p&gt;

&lt;p&gt;I’ve used Leaflet before, and mapbox, a little, so I’m going to start with those.&lt;/p&gt;

&lt;p&gt;Lets render a bare map, but open it up to about where the polyline will go. I’m sorta writing this blog post top down. Lets add a map, and initialize it.&lt;/p&gt;

&lt;p&gt;Following the &lt;a href="https://leafletjs.com/examples/quick-start/"&gt;Leaflet quick start&lt;/a&gt; docs, and soon making use of a &lt;a href="https://github.com/jieter/Leaflet.encoded"&gt;js/leaflet plugin&lt;/a&gt; that lets us decode polylines directly via &lt;code class="language-plaintext highlighter-rouge"&gt;L.Polyline.fromEncoded(polyline)&lt;/code&gt;. I was stressing about how to add JS without something like NPM, but then realized I can source the file directly in the head of the document&lt;/p&gt;

&lt;p&gt;Open up a new file on some directory - maybe &lt;code class="language-plaintext highlighter-rouge"&gt;leaflet_practice.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;We sourced there css, then JS, then added a div for a map, did a tiny bit of styling, and minimum JS. Telling the map to open on Denver’s approx lat/long.&lt;/p&gt;

&lt;div class="language-html highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;link&lt;/span&gt; &lt;span class="na"&gt;rel=&lt;/span&gt;&lt;span class="s"&gt;"stylesheet"&lt;/span&gt; &lt;span class="na"&gt;href=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"&lt;/span&gt;
     &lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="&lt;/span&gt;
     &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!-- Make sure you put this AFTER Leaflet's CSS --&amp;gt;&lt;/span&gt;
 &lt;span class="nt"&gt;&amp;lt;script &lt;/span&gt;&lt;span class="na"&gt;src=&lt;/span&gt;&lt;span class="s"&gt;"https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"&lt;/span&gt;
     &lt;span class="na"&gt;integrity=&lt;/span&gt;&lt;span class="s"&gt;"sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="&lt;/span&gt;
     &lt;span class="na"&gt;crossorigin=&lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/script&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;div&lt;/span&gt; &lt;span class="na"&gt;id=&lt;/span&gt;&lt;span class="s"&gt;"map"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&amp;lt;/div&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;style&amp;gt;&lt;/span&gt;
    &lt;span class="nf"&gt;#map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="m"&gt;180px&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/style&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setView&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;39.742043&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;104.991531&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tileLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://tile.openstreetmap.org/{z}/{x}/{y}.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;maxZoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;attribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;copy; &amp;lt;a href="http://www.openstreetmap.org/copyright"&amp;gt;OpenStreetMap&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;addTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;);)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here’s what that renders:&lt;/p&gt;





&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&amp;lt;script type="text/javascript" src="https://rawgit.com/jieter/Leaflet.encoded/master/Polyline.encoded.js"&amp;gt;&amp;lt;/script&amp;gt;
^------- this line is a critical addition
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;





&lt;div id="map1"&gt;&lt;/div&gt;



&lt;p&gt;There we go! It worked! A basic map. Pinch and zoom and pan. Cool, huh? Lets add the polyline next. We’ll assign to to a variable, and ask Leaflet to decode it and add it to the map.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;edit, that was really hard, what you’re about to see is a much smaller version of what I’d planned to do. It’s just a tiny fraction of the whole polyline, arbitrarily cut off at one end. I’ll explain what I did below.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;bleh, I didn’t even get the polyline directly encoded/decoded, I had to do an interstitial bit where I was working directly with lat/long pairs, as retreived from the decoder. Dang.&lt;/em&gt;&lt;/p&gt;

&lt;div id="map2"&gt;&lt;/div&gt;

&amp;copy;&lt;a href="http://www.openstreetmap.org/copyright"&gt;&lt;/a&gt;&amp;copy;&lt;a href="http://www.openstreetmap.org/copyright"&gt;&lt;/a&gt;

&lt;p&gt;Here’s what I did:&lt;/p&gt;

&lt;div class="language-javascript highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;map2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;map2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;setView&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mf"&gt;39.736532&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;104.977459&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tileLayer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://tile.openstreetmap.org/{z}/{x}/{y}.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="na"&gt;maxZoom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="na"&gt;attribution&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;&amp;amp;copy; &amp;lt;a href="http://www.openstreetmap.org/copyright"&amp;gt;OpenStreetMap&amp;lt;/a&amp;gt;&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;addTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;encodedPolyline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;u}pqFttt_S@NFNB?JA@@DIH@@CEEBAPAFBHK@@FEf@??FLEPA&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;?LD@CECDI?BBEH?DHLCT@BLBtB?@FADDDMADC@BJEIBAEMA@AC?HKACDDAHQEMA{@?e@BCR@FBJ?DBJAHB`@AF@LAHE~@?FEJLVC&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;?DNBA@BFBBAPMJAP@NCBE?CIO@Cn@Fl@Cj@BDEDBFALDN?^?HILCRHNAVD~@?XKz@k@HSBWHKVOD?b@VJBHA?KCQBi@n@Sp@e@`AMHIJDZB^?&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;KPKVYLq@f@i@Nm@L}@Cg@Q_AMWDH?CUa@g@YCEBGJQ`@]Eu@AA?HAG?f@CFSZITDHXXJPN`@BBAU}@s@_A][EEBAA?OEA}@JQL@F?EMAKLKBEFPd@Ma@QBA?D@EHKBKJKDILEGHe@@WAAEA@BBCADD@ABCK@UCW@IRO&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;GDGLABBF?TGD@TI^GX?@EDBJCNBFCLDHJLMBBBCL@NFDC@MH?JN@X@BD?NIJBh@SZ@HK@BVA@CDALDHHRAHBZBFEBYFO?MBSAECHDPANOIOC?CJIFDFB@DDEEA?DC?B@ALLKADACADC@AC@@GBAA?ECBARDPBKEHDA?IEA@@?C@F@AB?@FCKAF@D?C@@EC@A?BCC?E@P?CCB?GFAGB@CAA?BC?D??D@K?AAB@@AAC@@EDACD?CBBABCA@CADACB@CAC_@@UCKAABGAUAEICAEBa@FU@_@@ED?^BDA@IEQ?k@FCF?DBBGEOCWCAMAG]EAKBADGECECa@Bk@Dc@EaBDQE?CGD_@PIH@LHD?@BIFEEOAf@CDAAI@DBEF@IC?B?CFFDBBFF@LC@ICEQE?B?M@LCAD?EDAC@A@DA@FKACGCFBABCAEIHDB??DCABB@AEGDFEABFAC?CABECJHGBDXXPD`@L@HBDZ@NAJC@O?DOD@CM@BBABNAHEBI?GEKAM?QDDCL?RBJ?HIBKISR@CHBACBECDDG@BB?C@FCGKrC@&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;?BCABB@&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;?d@@@?C@H@AC?CF?&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;GFK?G@EA?CG?gABQFCFCn@Ft@JDT?BFBKNBEGGE?EGAMD@EJQBBFIJGB?@FAFC@B@ABHQ@BMN?H?KCA@@@A@@ABAAHEA?@B@GDCI@?BAEABCA?CB?CBBHGCB?CCBFC?@E?@BC?BCD@CC?BAA@?E@?@AC@AF@??C@G@LEEWFD??FHALD@DGF]CMR@SCAABDADDC@AEED@ADABDCBEA?@?CDAEB?NCCBEABBBDPXJFFDGJ?RGBEB@&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;R^LHJJ@d@Kb@E^MD?@@C@@IABDEDBj@?~@GNBJANDr@?TDXNNPPd@Fz@Gz@?f@BZAVBb@Cf@@HELG@?EDB@ADHBrBDz@KpA?~@Kv@a@|@_@Va@`@G?KES[KGKWGEa@CQEBDk@CYBKDc@XSAODUA]DSC[Bk@Ck@BUAUDIAAAUFQIYCIMK]MAODEDAGDBC?CIBUECE@DEACCFD@ACADE@AEFFGMFA?@IAI@AGBMCE@HBBKBG?DCDKLCG@@IBA@MZWA@?CGGCAAGKUAK@MCKIGM_@IA@KDEC?CHAAAD@BB?CA?B@CHJADBBCCC@CNGFBHJJMCC@DT?F?AF@FH?HGXBTFNKPFAAAG@CD?E@DH@@EAIDD?ACCFDDLGB?FELAAC?CMA?BJBMEKA?@A?DJHE?CCCFCAJED@ACA@@EHXFC?EEACKFQ?EEAM@AFC@L?LUHXD?ABCCCFAC@B@GKG?KOUAKKIq@RCD@DHB?@CBVUPo@D?A?BDNQCS@GDQHMCHDIJq@HMBMDI@KC??DBCH?EO?CBACKL_@HI?KCXICJMFS?GGCID@PPQA@GEE?HDHKHC^DBABGLENA@CFe@CMCk@CACD@BCAEIBMB@BGHCB@EM?KBI&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;CNBPYEGGB?MCCCLG?@CCHG@EE@CFA?DSFAJBEEDBXCF?V??@E@MFCOi@?KDCB@?FIB?BB`@CN?ADDHY@a@AAADEBAD@HFEC?@CEA@EB@AG@BPEJ@CGOGQ&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;AIDCA@EYBMFKH?E^BXAHAAGQFB@ACC?HHIDKC?@BDGB?@FGDE?CKCDAG?FHUYEIDBRHBDF?C?DAGBO?KD[KUEBIABDE@WAIBF?M?ADDv@D@FE?FHGJBBH?RDD@MGOGG?IB@C@@ABF@LHLJBFABKCIBJE@@BG@IJO@?BB?CDB&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;DVCDCME?@L@FAB?IFL@J?NEL@X?CCPCAADKAEBWOIBO?a@OG?OQQGG@ML@LEJCZ@@AD@AGJI`@EFCABA@BCH?CMJOBENIp@CDAHEDAAIDMH@@I@?B@AJF@PEFQDC?@@D?WHYRo@R?DQDKRQJG?UFIAm@HOEACDBABE??FIE@AJABKOJGNKACFFdBADG@@BEAZAp@KdCCRG@?AB@CDHLA^Dv@Gd@BNEDUJUBQDID?Ae@C?IBECCC@FGPDTKBIKVy@?UFQF]GMKK[QMAOFYVE?OHO@ULSRGBSb@^r@RXJDXD^ALBhAEDWCI?SC?EBCJ?JDEE@B@BCACC??BDEB@C@FC@CM@GFAABBNKFK@@G?KBIL@@XMDIGH@@BA?OMGMFGFYh@CNAAAEADIDIGSA[HUCGEQ_@_@Y_@q@c@NEDUD]LI?MC}@@e@GUAGDEXUXg@Xa@NGFc@AOBY?k@Ig@Fq@A]DCDc@AY@GGQD_@@GLC@eA@OCqCG}@HO?ICc@DYGMBEEM?MLSE_@?SDm@EO?IDGEG?KFFl@?n@K@QC]@OAKFAFFh@I&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;?HDBTCBCEAE@@F@B?Da@PJEKAACDAVWd@[??ALBFNIWHDA@FFA?GGABICC?@s@FSAj@@J@BFCEQCs@DLGH?GBF?C@F?EAB?C?BACAC@?CCBBDHBFAOABA?ACASDH?BBFA@CBBH?KGM@HAMABDF@ICB?EEDDC@?AFAGAB?AAC?D?AB@AO@JDcCP|BWF?DFCGAB@EDLEKFD?@IGF?ED?CAAFHAEA@ACCFECC@?CIDBEGGABBBJAH@@CSDREBMGJB@CAB@AAABBBCGABB??BCBUJFKB@C?CCTIYDEFFEZAICWJAKD@HEF@AB?EDDAHHKCGOJ?BHCENNM@EA?AAD?C@?C?D?E?B@AA??A?DH??BKC@C@DCE@D?EBBCCM@F?AAFBAA@?C@@BA?BCA?@??CA@@AA?@@?A?@@AA?@?CA@BGEI@ABR?KHB?ACF?CIFWIFADB?JEKHDDECDE@HIDFA?MABD?GNEDNQCC@@GDE?BAFA?BD?MBCBBAAAFKA@@FAA@C@BCBI@DEBAABB?@AAE?BC?@EB@GADD?@AA@BGSFRAA@A?H@AAECJEFFE@M?H@AAI@BDAG?@@AC?@CABB?A@?A@@EC??CC?F?CJB?CACB@?E?AGFECBEEHB@FABACJ@BCC@KKC@BGF@A@ACDFAF@DAI?@E@DGDA?@CBCAADBG?BAAAFEAF@AAEBB@?EC@P@BBI@EKBKC?AHBFCK?DBBHAGAADO@H??BD?C?@ADBCGD@YJXAIKBA?EGFBG?B@CA@AC?BC@CCDC?ADAEJB@ACC@B@CBN?EC?BGD?FBEI@B?@IM@G?NIH?DDIIHD?EAD?ICBIAHACDBBA?H@EBLAO?BAG?BCC@B@GED?ABFB?D@CEADEM?AABCDDKAL?CBBA@FEC?CI?B?A??EB@EALBABBC?BE@DAA?@@A@BAD?E@D?WDF?FE?BEAFBCABAA@ECDBCEEDB?ACD?ABDAIBD@AABAC?BAI@F?AGC??@DAC@DDA@BCCBE?AIH@@J@AADB@?KE?DFC?EEKCD?ADD@AA@AD?E@BAGCL?G?A@@ABBE?@EIAL@AB@BAG@@CAF@O?@@C?@@DA?CC@B?C?@A@?CB?EB?CAD?SAh@FK?MCMBF@?@B??EBCD?OABCNBKB?BH??CL@Q?@E@FC@HBI?AAE?HAAAGBFB?DB@GCBCEAH@G?B@CBDCA?BAAA??G@FA?CC@BAGAIBT@QBl@GKAO@B?A?F@UBD@BAI?FMCNBOEHDEKDJIMDFBWDTBDC@GAAB?ECF@EF@GCJBMCF@EAHACF@HCG?F@K?@DE?BMFDCAACUBRACBN@QDDACGBAI?H?C@B@?I[JH?A@NCKCS?P@VE@@@J[MJ?HFEDYAN?@E@@FCL?FCK?QBJ@O@b@@MIM@LDICJE@KA@@@AFG@DFE@@@E?AE@?CCBAI?FJF?E??ICEF?C??HCABC@F@CC??CE@FAAE?D@AC?@@F?G@@DC?AE@@?@@AGAH@A??C@?A?CBFEIB@?CED@AECHB?C?FCCAB?AB?CA?@@ADCARAG?BBIAD@A?DEIFFCG@BEB?E@?AEHDE?ECB?DBAG?DAA@B@@AEA?C@BEDD?@E?BBAAC?BC?AED@GDDE?BBC?BGCLGM@A?FACHEEBAE?D@A@D?A@?CD?M@F?A@HBCA@GCB?CC@D@C?@@A?@B?ADCICCDD@@CBHOAJAAAB?@EG@FNAA?EEAA@BCC?FACEB@E@BAAE?FCBJEC@B@SBLD?CEADDDCI?BA?CCA@??B@CB??BCB@AAAB@CAB?C@@CB@C@B?I?@?@BGAJIE?DEEAHFE@A?BCE@J?CAD?A?@AG?@@ECHDOB?BHDCA?G@?A@FAG@?@D@KJDEACBABC@@C?@BEAD@?@CABB@CK?HCKDB?A@BEB??@CABAAAEDHAGADA?CC@@@C?EB@?AABB@C@@C?BAAAJ@C?EADAEAA?F@EAD?IDDAEAD@A?@?@AE@BA?@C@B@ABEABCCADACBG?J?B?CA@?C?ACD@BAC@@ACA@BC?ECB@C@D@C@DAEAF?@@A@EA@CB@E?FAIDECE?ABCA@ANDBAEAD@CC@CC?D?E?EDNBC@@B?EEA?@B?G?DAADAA@CHA@@E?ABCABAA?DB?@CC@CBBE@D@CADAEA@EA?@@A@@HI@DIA??ED??BC@@CCABEACGDC?@A@BH@?DC@JBECDCGCBCG@IA@?C@@@BAACD?BCEABABBAHLB?GC@?FQCFA?ARFEBD?OAFACDCA@CG@F@C?CE@A?BD?C@AAH?@CK@D@FAACBA?CEBB@@AQADACDG?L?ACA?DBCFDBABJ?KEEEI@HADB@AACG@B?@@A?BAAANCB@E?AFE@B@E?JCCB@@CA@C@?A?BAAC@?I@?DI@BB@CCA@EEE@@?F@A?B@A?BDA?CA?@AK@B?DIE@BFB?EBBAC?D?BA?ACAB?C@D?@D?ICACBHDGDEAEGG?T@GI@?C@CAABF@DCE?D?A?@@C?D@BA@?A@C?@BE?BBA??@B@ACB@EA@AD@KAPBKE@??@C?DACCB@C??@DBEADDEA@BA@B?G?CCD?C@@CBAGCBAI@?BF@AC@@A@@AEAAEC?DBA??AA@D?BKOAPBM@LBC?D?K@L@C?@B@?CCJ?ICE@FAGAJ@O?@AC?BA@BF@M?AGCCD?OCA@DBNDEAAJ@A@BAEDBIGDA@@E?BDCEAD?AD?CA@ADFAGA?B@QMLFBBA@?AEBACHAG@@??B@C@@C@BB@CEB@EC?DABFEC?@@ACE?BACIAB??BAADCAC?BC@B@?@E@F?@@CEDAA@?CIAJ@A@E@J?K?D?C?PFMAFDJ@ECFA?AQMOH@?AE@BDAA?B@FCADB@C@A@?CBC@@GA@AABBCDDC@B?ACQ?H@?BC@E?PEAABAC?ABCCBECDHFMBBEK@C@@BPGD@Q@AF@@BCLSDAA?@@KBB?CD@AGBJAQ@DCRAI@CE@@GBD??EDAJDCFYCGCCBLCCC@BHAC@B?A@DECBB@CA@AAB@CA@B?AAABBA?@AA?@W@NCC?@?EFa@DFIAA`@??GPCEBBDCACBBAA?@?ACABB@?GCDB@C?@AF?M@BA@D?CC@HCCB@?AC@C?@BAI@DCMCE@BLB??FC@BCCQPGJ?EFMD@HFGCAF@MD?ABCH?S?@DDDBAG@@G@??EC@CAB??BFI@?EDE@@FFS@BCDG?CDCCJD?DCA@CCGBCDBCA?B@ELDCEHBGA?@D?EAEDBCAAC@?BEBMEREB@KDBCF?C@DCC?AE@DDGG?KFGALCB@AB?AB?G?F@ZGKAYHVGB?ODEAAJD@?FB@BCACEC@CABE?@CC?IMFVL??IE??@AA?@?EC?B??D?E?@@A?BCC@FBDAE?DB?GK@??@?AAA?B?C@?@NDCCFBEFEEGM??BD@A@E?FCMCE?J?@BBCI@HCD@I?ABD@?C?BCAF@M?ABBBFAAA@BBAC@?EBBCAIBBKDACA?@E?BBAEEDICNBO??CD?BBGBB@F@CCBCG?B?ADFFBCC@BI@@CCDDI@@ACAAGQFJADBDGB@ECC@PFGDIATCE@ICBCD@AAE?C@@@C?AAHAIILAJDQ@A?FBABQ@?DC?LA@KC@B?@?ACBAE?ABB@KBB?CAPAE?@AGAH@C@?DB?ACFAAEK?J@M?GBD?ADBAABHACEBAFDK@D@O@EC@AECHCJ@C?D?ACDAGFDB?ACCGB@@M?b@?I@DFD@?ED@CCH@Q@EA?A@@C@QESH?IJ?VEDIDCYBTGUJLADDCGBFA??DAEBECGCBHDJ@I?Gn@?YH?CEEBEA?ADAEFE@AAFEB?BFCFGHECEO@GNGCEGBC?FKJD@HDBV@HIKAOFIC?BHBBAI?DCACKGE@?@FBEFAKKMHAK?R?SCNB?BHNADB?BFFBD?c@GHEBEA@QAMBF?&lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="s2"&gt;JAEG@@CH?BEMAHIEBCK?BD@GTBDGGJGGBBC@??EFHYAHAA@BBPD?EKE?@ACB@CBBCA?@??CAB@@@AEA@?AB?CBBAC@DEABBC@BAA??GDDAB@GC??@?CCDBEBB?DCI@B?CABAC@@A?@??AAB@C?D?E?B@CA@B@C??A?@BAA??DC@@CC?BE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;


&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;🙄&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;decodedPolyline&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Polyline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEncoded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;encodedPolyline&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;L&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;polyline&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decodedPolyline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_latlngs&lt;/span&gt; &lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;color&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;opacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;linejoin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;round&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addTo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;map2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;omg&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nx"&gt;map2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;decodedPolyline&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;_latlngs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;~I’m still doing something funky to escape the escape characters in the polyline.~ Able to get around that with &lt;code class="language-plaintext highlighter-rouge"&gt;JSON.stringify()&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you’d like to see the raw html that is being used to generate this exact page, it’s available in an &lt;em&gt;extremely&lt;/em&gt; hacky way &lt;a href="https://github.com/josh-works/josh-works.github.io/blob/main/_posts/2024-09-10-polyline-practice-01.md?plain=1#L179"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Next time, might animate a marker moving along the line, something like &lt;a href="https://github.com/openplans/Leaflet.AnimatedMarker?tab=readme-ov-file"&gt;https://github.com/openplans/Leaflet.AnimatedMarker?tab=readme-ov-file&lt;/a&gt;, or maybe make the line blink, or see if we can give a sense of which direction the movement was happening in.&lt;/p&gt;

&lt;h3 id="useful-additional-resources"&gt;Useful additional resources&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Get the ‘decode raw polyline’ function in your page &lt;a href="https://github.com/jieter/Leaflet.encoded"&gt;with this package&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://markhneedham.com/blog/2017/04/29/leaflet-strava-polylines-osm/"&gt;Leaflet: Mapping Strava runs/polylines on Open Street Map ()&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Josh Thompson</author><pubDate>Tue, 10 Sep 2024 16:00:00 GMT</pubDate><guid isPermaLink="true">https://josh.works/polyline-practice-again</guid></item><item><title>Why Github actually won</title><link>https://nicolaiarocci.com/why-github-actually-won/</link><description>&lt;blockquote&gt;
&lt;p&gt;In the end we won because the open source community started to converge on distributed version control and we were the only ones in the hosting space that truly cared about how developers worked at all. The only ones who questioned it, approached it from first principles, tried to make it better holistically rather than just throwing more features onto something existing in order to sell it.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Full story &lt;a href="https://blog.gitbutler.com/why-github-actually-won/"&gt;here&lt;/a&gt;. A great run-down by Scott Cahon himself on why Git and then GitHub won the version control system war.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Tue, 10 Sep 2024 12:15:01 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/why-github-actually-won/</guid></item><item><title>On this day, September 10</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-10/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2009 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Tue, 10 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-10/</guid></item><item><title>Tiny Great Languages: BASIC</title><link>https://zserge.com/posts/langs-basic/</link><description>This is part 2 from series &amp;ldquo;Tiny Great Languages&amp;rdquo;.
Final code is on Github. Part 1: Assembly. Part 2: BASIC. Part 3: Forth/MOUSE. Part 4: Lisp. Part 5: APL/K. Part 6: PL/0. Meet BASIC, the king of home computing in the late 1970s. Originally designed to promote computer literacy in schools, BASIC inspired a whole generation of professional software engineers.
BASIC typically combined a simple text editor with a command shell and interpreter.</description><author>zserge's blog</author><pubDate>Tue, 10 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zserge.com/posts/langs-basic/</guid></item><item><title>5 TV Shows Everyone Has Watched But Me</title><link>https://huphtur.nl/5-tv-shows-everyone-has-watched-but-me/</link><description>&lt;ol&gt;
&lt;li&gt;The Wire&lt;/li&gt;
&lt;li&gt;Mad Men&lt;/li&gt;
&lt;li&gt;The Office&lt;/li&gt;
&lt;li&gt;Breaking Bad&lt;/li&gt;
&lt;li&gt;The Sopranos&lt;/li&gt;
&lt;/ol&gt;</description><author>huphtur</author><pubDate>Tue, 10 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://huphtur.nl/5-tv-shows-everyone-has-watched-but-me/</guid></item><item><title>The Quality Without A Name</title><link>https://one.mikro2nd.net/pages/qwan/</link><description>Tall buildings are all awful. And the reason is, they&amp;rsquo;re built, not for the people who will inhabit or use them, but for the landlord who will rent out the space. They&amp;rsquo;re designed for a different set of needs and wants than the people who must occupy them, so they inevitably lack the Quality Without A Name. See also: airports, shopping malls.
The same applies to multi-tennanted software.
Hard-architecture — houses, factories, town-halls, airports — must meet three needs: the building must fulfil the functional requirement, it must be buildable, and it must be (at least minimally) inhabitable.</description><author>one mikro2nd</author><pubDate>Tue, 10 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://one.mikro2nd.net/pages/qwan/</guid></item><item><title>On this day, September  9</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-9/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2011 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Mon, 09 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-9/</guid></item><item><title>What is the best pointer tagging method?</title><link>https://coredumped.dev/2024/09/09/what-is-the-best-pointer-tagging-method/</link><description>In this post, we are going to take a deep dive into pointer tagging, where metadata is encoded into a word-sized pointer. Doing so allows us to keep a compact representation that can be passed around in machine registers. This is very common in implementing dynamic programming languages, but can really be used anywhere that additional runtime information is needed about a pointer. We will look at a handful of different ways these pointers can be encoded and see how the compiler can optimize them for different hardware.</description><author>Core Dumped</author><pubDate>Mon, 09 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://coredumped.dev/2024/09/09/what-is-the-best-pointer-tagging-method/</guid></item><item><title>Making progress on side projects with content-driven development</title><link>https://ntietz.com/blog/making-progress-with-content-driven-development/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;It's hard to make progress on side projects sometimes.
Getting started is easy when we see the bright future of the project.
Then somewhere in the middle, we get stuck in lists of tasks to do, a long way in and still a long way from the finish line.&lt;/p&gt;
&lt;p&gt;This happens to me as much as anyone.
In the last couple of years, I stumbled into a way to avoid getting stuck midway in my projects.
It's not just about productivity: this also lets me &lt;em&gt;let go&lt;/em&gt; of things when I reach a good stopping point.
It helps me figure out what that stopping point should be.&lt;/p&gt;
&lt;h1 id="where-projects-get-stuck"&gt;Where projects get stuck&lt;/h1&gt;
&lt;p&gt;There are a lot of reasons that projects get stuck.
Maybe we don't know how to do something, and it's a big hurdle to overcome.
Or maybe life gets in the way, we put down the project, and we never pick it back up.
Or we make progress, but we keep adding features that we think it "needs."
Or as time drags on, we just kind of lose interest and the project peters out before it ever got shipped.&lt;/p&gt;
&lt;p&gt;For me, each of these shares a common characteristic: &lt;strong&gt;an overwhelming task list&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;It can be overwhelming for different reasons.
If the next task is something we don't know how to do, then even a short list can be overwhelming—it's tough to decide to work on something you have no idea how to approach!
Or if the task list is a lot and you put it down for some time, it can be overwhelming to decide where to dive back in.
And when it's just a big project, it's hard to keep interest levels high for the duration, so eventually there's a point where that interest dips below what you need to keep going.&lt;/p&gt;
&lt;p&gt;I've had each of these happen to me, but losing interest from a big project over a long time is the most common.
My interests can shift around a lot and I like to do varied things, so if a project takes too long, it might end up on the shelf, half finished, never to be worked on again.
Besides, I usually started it to learn something, and the rest of the project can feel like a lot of busy work to get there.&lt;/p&gt;
&lt;p&gt;But if I get a quick win with something, that dopamine hit can keep the interest up, and make it easier to keep going.&lt;/p&gt;
&lt;h1 id="sequencing-via-content"&gt;Sequencing via content&lt;/h1&gt;
&lt;p&gt;When I started writing more regularly, I noticed that I was also making more regular progress on my projects.
This was not just because I wanted things to write about, but because writing about my projects &lt;em&gt;changes how I approach them&lt;/em&gt;.
Instead of building one big task list, I think about what small things I can work toward to write about.&lt;/p&gt;
&lt;p&gt;You see this in workplaces sometimes.
One form is with agile development in general, where each sprint is supposed to result in something shippable to deliver value.
This can also look like demo-driven development, where each sprint you try to get a good demo ready to show off.&lt;/p&gt;
&lt;p&gt;What these have in common, and why they work, is that they make you sequence things into smaller deliverable chunks.
When I'm working on a project I want to write about, I think about which pieces I can work on independently to get something that is worth writing about, worth explaining to people.
Maybe that means that for this upcoming project, I'll do the parser first, and write a post about some of the neat things with parsers!
Or maybe it means that we cut &lt;em&gt;out&lt;/em&gt; things like permissions for a demo web app, since that's not at all core to what we're doing (where what we're doing is &lt;em&gt;not&lt;/em&gt; deployed software).&lt;/p&gt;
&lt;p&gt;By thinking about the sequence for sharing updates, it is a lot easier to cut away the cruft and focus on the core.
Each time you get to share your work, that can give a good dopamine hit.
It also gives a good motivation for some of the things you might not be as excited about!&lt;/p&gt;
&lt;h1 id="where-to-share-progress"&gt;Where to share progress&lt;/h1&gt;
&lt;p&gt;A lot of my updates are posted on my blog or in my newsletter, but there are so many ways to do content-driven development!
It mostly comes down to where &lt;em&gt;you&lt;/em&gt; want to share your progress.&lt;/p&gt;
&lt;p&gt;A few formats that I've seen work well are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Blog posts&lt;/strong&gt;: this is my default, because I like blogging!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;YouTube videos&lt;/strong&gt;: if you're into video instead of writing, you can also make demo and update videos and post those.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microblogs&lt;/strong&gt;: a lighter weight alternative to blogging, posting on places like Mastodon can give a good way to share updates.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forums&lt;/strong&gt;: I'm specifically thinking of the Recurse Center forums/chat tool where people post check-ins periodically with progress. These sorts of groups can be great for sharing!&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;With a friend&lt;/strong&gt;: You can also just share periodically with one or two other people! It doesn't have to be very public.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As I wrote this out, I realized that I do almost all of these.
I ultimately end up with blog posts, but along the way I share tidbits on Mastodon and in my RC threads, and I tell friends about exciting things as I go.&lt;/p&gt;
&lt;h1 id="you-decide-when-you-re-done"&gt;You decide when you're done&lt;/h1&gt;
&lt;p&gt;Projects don't last forever, at least until we figure out immortality.
One bonus of working on projects through the lens of writing about my progress/learnings is that I stop more intentionally.&lt;/p&gt;
&lt;p&gt;Without this lens, I look at all the features I don't have as something that is wrong, where I've failed.
With this perspective, though, I look at the features I don't have as where I chose to stop because it was orthogonal to my goals!&lt;/p&gt;
&lt;p&gt;If you are focused on what you want to learn and how to share or communicate that, then you'll have that in mind as you pick what to work on.
This will let you be aware of the things that are &lt;em&gt;not&lt;/em&gt; important to you so you can let go of them.
You can separate the wheat from the chaff and just get what is really important, then abandon the rest.
It's freeing!&lt;/p&gt;
&lt;p&gt;Let the projects continue as long as they need to for you to get what you want out of them.
Once you've learned what you came for, demonstrated what you wanted to, or made that useful little tool for yourself?
Then you can just be... done.
Even if the task list isn't.&lt;/p&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 09 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/making-progress-with-content-driven-development/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Nightly Rust development with Nix</title><link>https://paperless.blog/nightly-rust-development-with-nix</link><description>This one took a while to get simple enough; enjoy!</description><author>Paperless</author><pubDate>Mon, 09 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://paperless.blog/nightly-rust-development-with-nix</guid></item><item><title>FastHTML Loading Spinner</title><link>https://www.danielcorin.com/til/fasthtml/loading-spinner/</link><description>FastHTML Loading Spinner</description><author>Thought Eddies</author><pubDate>Sun, 08 Sep 2024 15:18:23 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/fasthtml/loading-spinner/</guid></item><item><title>Unstable: Season 2</title><link>https://olshansky.info/tv/unstable_season_2/</link><description>Olshansky's review of Unstable: Season 2</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 08 Sep 2024 04:52:38 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/unstable_season_2/</guid></item><item><title>On this day, September  8</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-8/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2009 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Sun, 08 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-8/</guid></item><item><title>35% Discount on Keyword Arguments in Python 🐍</title><link>https://ashvardanian.com/posts/discount-on-keyword-arguments-in-python/</link><description>Keyword arguments in Python aren't free. Switching from PyArg_ParseTupleAndKeywords to METH_FASTCALL and manual parsing yields a 35% speedup in function calls.</description><author>Ash's Blog</author><pubDate>Sun, 08 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ashvardanian.com/posts/discount-on-keyword-arguments-in-python/</guid></item><item><title>Using an iPod Classic Monochrome 4th Gen in 2024</title><link>https://thomashunter.name/posts/2024-09-08-using-ipod-classic-4th-gen-in-2024</link><author>Thomas Hunter II</author><pubDate>Sun, 08 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://thomashunter.name/posts/2024-09-08-using-ipod-classic-4th-gen-in-2024</guid></item><item><title>Tiny Great Languages: Assembly</title><link>https://zserge.com/posts/langs-asm/</link><description>This is part 1 from series &amp;ldquo;Tiny Great Languages&amp;rdquo;.
Final code is on Github. Part 1: Assembly. Part 2: BASIC. Part 3: Forth/MOUSE. Part 4: Lisp. Part 5: APL/K. Part 6: PL/0. Decades ago, I wrote my first interpreter &amp;ndash; a Turtle Graphics IDE designed to help kids at my school learn programming. I built it in Pascal, and to this day, I still wonder how I managed to make it work without any formal knowledge of writing interpreters.</description><author>zserge's blog</author><pubDate>Sun, 08 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zserge.com/posts/langs-asm/</guid></item><item><title>Exploration</title><link>https://gyani.net/blog/california/</link><description>.. what is going on - 
		  Alright so I left my job two weeks ago. 22nd of August was my last day at Kurtosis. Its been a little over two weeks and I have decided to start writing on this blog again. I want to write about my learnings and any experiences I have had in...</description><author>Gyanendra Mishra</author><pubDate>Sat, 07 Sep 2024 21:30:00 GMT</pubDate><guid isPermaLink="true">https://gyani.net/blog/california/</guid></item><item><title>Talk to your vendors</title><link>https://ernest.oppet.it/2024/09/07/talk-to-your-vendors/</link><description>With the growth of self-service SaaS applications and open-source tools, we&amp;#8217;ve all become used to introducing new vendors &amp;#38; dependencies into our stack without talking to anyone. As anyone who has been on the receiving end of sales discovery calls will attest, there are advantages to this. It&amp;#8217;s faster to get started and try out [&amp;#8230;]</description><author>Ernest Oppetit</author><pubDate>Sat, 07 Sep 2024 13:33:22 GMT</pubDate><guid isPermaLink="true">https://ernest.oppet.it/2024/09/07/talk-to-your-vendors/</guid></item><item><title>3 Body Problem: Season 1</title><link>https://olshansky.info/tv/3_body_problem_season_1/</link><description>Olshansky's review of The 3 Body Problem: Season 1</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 07 Sep 2024 11:52:36 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/tv/3_body_problem_season_1/</guid></item><item><title>On this day, September  7</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-7/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2007 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Sat, 07 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-7/</guid></item><item><title>Seek and you shall find</title><link>https://xenodium.com/seek-and-you-shall-find</link><description>&lt;p&gt;A couple of months ago, I &lt;a href="https://xenodium.com/ready-player-mode/"&gt;introduced Ready Player Mode&lt;/a&gt;, an Emacs major mode used to peek at media files from my beloved text editor. The goal was simple. Treat opening media files like any other file, that is, open and go.&lt;/p&gt;
&lt;p&gt;The initial implementation served me well while reviewing lots of tiny audio files I used to practice learning a new language. At this point, I started thinking, could I use ready-player for regular music consumption? The thing is, long ago I had stopped buying music and relied on streamed music from online services. Could I go back to offline?&lt;/p&gt;
&lt;p&gt;Dusting off my old media collection brought lots of memories as I rediscovered tunes. Having said that, the ready-player experience wasn't quite cutting it for an extended listening experience. I no longer wanted to occasionally peek at media to learn a language. I wanted to load a full music collection. I wanted random access to everything. I wanted Emacs to remember what I was listening to across sessions… While I did &lt;a href="https://xenodium.com/the-dired-abstraction/"&gt;add some pluggable flows&lt;/a&gt;, I still needed additional changes to make the experience more pleasant.&lt;/p&gt;
&lt;p&gt;While plugging away at my own ready-player's pet peeves, I also collected a handful of feature requests. Let's go over the latest features.&lt;/p&gt;
&lt;h2&gt;Seek (f/b binding) - &lt;a href="https://github.com/xenodium/ready-player/issues/11"&gt;feature request&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While not a feature I initially thought ranked highly in priority, I now find myself seeking audio files from time to time. Ready Player delegates all playback to the likes of &lt;a href="https://mpv.io/"&gt;mpv&lt;/a&gt;, &lt;a href="https://www.videolan.org/vlc/"&gt;vlc&lt;/a&gt;, &lt;a href="https://mplayerhq.hu/design7/news.html"&gt;mplayer&lt;/a&gt;, and so on… Up until now, interacting with these utilities merely consisted of feeding a media file path on to the respective process.&lt;/p&gt;
&lt;p&gt;Command line utilities like &lt;code&gt;mpv&lt;/code&gt; offer socket communication via &lt;code&gt;--input-ipc-server&lt;/code&gt; to enable further requests like seeking forward and back. Ready player now supports seeking via mpv. Maybe support for other utilities can be added in the future.&lt;/p&gt;
&lt;p&gt;If you're on a recent version of ready-player, seeking is automatically enabled if you've got mpv installed and aren't explicitly customizing &lt;code&gt;ready-player-open-playback-commands&lt;/code&gt;. The default value takes care of things:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defcustom ready-player-open-playback-commands
  '((&amp;quot;mpv&amp;quot; &amp;quot;--audio-display=no&amp;quot; &amp;quot;--input-ipc-server=&amp;lt;&amp;lt;socket&amp;gt;&amp;gt;&amp;quot;)
    (&amp;quot;vlc&amp;quot;)
    (&amp;quot;ffplay&amp;quot;)
    (&amp;quot;mplayer&amp;quot;))
  &amp;quot;...&amp;quot;
  :type '(repeat (list string))
  :group 'ready-player)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/seek-and-you-shall-find/seek.webp" /&gt;&lt;/p&gt;
&lt;h2&gt;Pause/resume (SPC binding) - &lt;a href="https://github.com/xenodium/ready-player/issues/10"&gt;feature request&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Until now, ready-player could only play and stop, so you always had to start playing tracks from the beginning. With &lt;code&gt;mpv&lt;/code&gt; ipc support now in place, adding pause/resume was a breeze. Like seek, it should just work for ya if &lt;code&gt;mpv&lt;/code&gt; is on your system and no explicit customization of &lt;code&gt;ready-player-open-playback-commands&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Repeat current file (r binding) - &lt;a href="https://github.com/xenodium/ready-player/issues/16"&gt;feature request&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While repeating current playlist (or directory) was already supported, there was a feature request to enable repeating files. Toggling repeat now cycles through available modes.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/seek-and-you-shall-find/repeat.webp" /&gt;&lt;/p&gt;
&lt;h2&gt;Selective players - &lt;a href="https://github.com/xenodium/ready-player/issues/13"&gt;feature request&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With ready-player delegating to a single utility for either audio or video playback, folks may have a need to specify different utilities for either of these two. While I'm happy for &lt;code&gt;mpv&lt;/code&gt; to handle both audio and video, we now have a couple of prepending options.&lt;/p&gt;
&lt;h3&gt;Use a predicate function&lt;/h3&gt;
&lt;p&gt;Prepend each utility with either the built-in &lt;code&gt;ready-player-is-audio-p&lt;/code&gt; or &lt;code&gt;ready-player-is-video-p&lt;/code&gt; functions, or maybe create your own predicate helper.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(setq ready-player-open-playback-commands
      '((ready-player-is-audio-p &amp;quot;ffplay&amp;quot;)
        (ready-player-is-video-p &amp;quot;mpv&amp;quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Use an extension list&lt;/h3&gt;
&lt;p&gt;In this example, we delegate mp3 and ogg playback to ffplay and everything else to mpv.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(setq ready-player-open-playback-commands
      '(((&amp;quot;mp3&amp;quot; &amp;quot;ogg&amp;quot;) &amp;quot;ffplay&amp;quot;)
        (&amp;quot;mpv&amp;quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Autoplay (a binding) - &lt;a href="https://github.com/xenodium/ready-player/issues/9"&gt;feature request&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Automatically start playing once file opens. No need for user to explicitly request playback.&lt;/p&gt;
&lt;h2&gt;Mark in dired (m binding) - &lt;a href="https://github.com/xenodium/ready-player/issues/8"&gt;feature request&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Open a dired buffer and mark the currently played file.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/seek-and-you-shall-find/mark.gif" /&gt;&lt;/p&gt;
&lt;h2&gt;M3u playlists - &lt;a href="https://github.com/xenodium/ready-player/issues/14"&gt;feature request&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While I talked about how &lt;a href="https://lmno.lol/alvaro/the-dired-abstraction"&gt;the dired abstraction&lt;/a&gt; made basic m3u playlist support possible, it wasn't until recently that I included this experiment in the package itself. In addition, .m3u are now recognized by Emacs and automatically open like any other file: find-file, dired, projectile…&lt;/p&gt;
&lt;h2&gt;Load recursive directory&lt;/h2&gt;
&lt;p&gt;With &lt;a href="https://lmno.lol/alvaro/the-dired-abstraction"&gt;the dired abstraction&lt;/a&gt; at its core, ready player can load any dired buffer. You could do something like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;M-x find-dired RET&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Pick a directory. RET.&lt;/li&gt;
&lt;li&gt;Type &lt;code&gt;&amp;quot;-iname \*.mp3 -o -iname \*.ogg -o -iname \*.m4a&amp;quot;&lt;/code&gt; RET.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;M-x ready-player-load-dired-buffer&lt;/code&gt; RET.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While uber flexible, there's no need to regularly do that, so you can now invoke &lt;code&gt;M-x ready-player-load-directory&lt;/code&gt; and it will recursively find all media files in it.&lt;/p&gt;
&lt;h2&gt;Toggle player view (C-c m m binding)&lt;/h2&gt;
&lt;p&gt;While we can always get back to the player buffer via our favourite buffer-switching mechanism (I like &lt;a href="https://github.com/abo-abo/swiper"&gt;ivy&lt;/a&gt;'s ivy-switch-buffer), we now have &lt;code&gt;M-x ready-player-view-player&lt;/code&gt; available for quicker toggle.&lt;/p&gt;
&lt;h2&gt;Remember session&lt;/h2&gt;
&lt;p&gt;Playback is now remembered across Emacs sessions. Toggling player view (C-c m m binding) or playback (C-c m SPC binding) starts the last song you were playing on your previous Emacs session.&lt;/p&gt;
&lt;h2&gt;Index + searching (/ or C-c m /)&lt;/h2&gt;
&lt;p&gt;We now have automatic indexing, which enables richer searching across your collection, not to mention that random access I was craving.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/seek-and-you-shall-find/search.webp" /&gt;&lt;/p&gt;
&lt;h2&gt;Global bindings&lt;/h2&gt;
&lt;p&gt;Last but not least, you may have noticed a handful of key bindings throughout the post. Single-character bindings all work within a ready-player buffer. Bindings prefixed &lt;code&gt;C-c m&lt;/code&gt; are now globally available when &lt;code&gt;ready-player-mode&lt;/code&gt; is turned on. This can be customized via &lt;code&gt;ready-player-set-global-bindings&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;Please help make it all self-sustainable&lt;/h2&gt;
&lt;p&gt;If you find this package useful or got the features you wanted, please &lt;a href="https://github.com/sponsors/xenodium"&gt;consider sponsoring the work&lt;/a&gt;. I've left my tech job (maybe a post for another time) and looking to make projects like ready-player self-sustainable.&lt;/p&gt;
&lt;p&gt;If you're an iOS/macOS user, you can also &lt;a href="https://apps.apple.com/us/developer/xenodium-ltd/id304568690"&gt;buy my apps&lt;/a&gt;. Here's another freebie (&lt;a href="https://github.com/xenodium/macosrec"&gt;macosrec&lt;/a&gt;) I've put out there, which I &lt;a href="https://lmno.lol/alvaro/recordscreenshot-windows-the-lazy-way"&gt;regularly use to capture Emacs demos&lt;/a&gt; for this blog.&lt;/p&gt;
&lt;p&gt;You may also enjoy this blog and all the tips I share. Blog posts take time. Consider &lt;a href="https://github.com/sponsors/xenodium"&gt;sponsoring my blog&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I've built other Emacs packages you may already use or would like to. Maybe I already built a feature request? Consider &lt;a href="https://github.com/sponsors/xenodium"&gt;sponsoring&lt;/a&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/chatgpt-shell"&gt;chatgpt-shell&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/dwim-shell-command"&gt;dwim-shell-command&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/sqlite-mode-extras"&gt;sqlite-mode-extras&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/org-block-capf"&gt;org-block-capf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/company-org-block"&gt;company-org-block&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/xenodium/ob-swiftui"&gt;ob-swiftui&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm also building &lt;a href="https://lmno.lol"&gt;lmno.lol&lt;/a&gt;, a new blogging platform, with &lt;a href="https://indieweb.social/@xenodium/112265481282475542"&gt;drag and drop to the web&lt;/a&gt;. Maybe you want to try that too? &lt;a href="mailto:help/at/lmno.lol"&gt;Get in touch&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Thank you!&lt;/p&gt;
&lt;p&gt;Álvaro&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Sat, 07 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/seek-and-you-shall-find</guid></item><item><title>Photo of the Day: Garmin Instinct Solar</title><link>https://developer.run/79</link><description>&lt;p&gt;This is post of appreciation of my Garmin Instinct Solar watch, which has travelled the world with me and been an indispensable tool in many &lt;a href="https://developer.run/60"&gt;adventures&lt;/a&gt;. It has GPS recording, altimeter, thermometer and sunset time, but thanks to the monochrome display, the battery lasts for month (+recharging from the sun), not like your silly "smart" watch :p&lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.run/pic/garmin/garmin1.jpg"&gt;&lt;img alt="🇫🇷" src="https://developer.run/pic/garmin/garmin1-200x.png" /&gt;&lt;/a&gt;
&lt;a href="https://developer.run/pic/garmin/garmin2.jpg"&gt;&lt;img alt="🇮🇹" src="https://developer.run/pic/garmin/garmin2-200x.png" /&gt;&lt;/a&gt;
&lt;a href="https://developer.run/pic/garmin/garmin3.jpg"&gt;&lt;img alt="🇫🇴" src="https://developer.run/pic/garmin/garmin3-200x.png" /&gt;&lt;/a&gt;
&lt;a href="https://developer.run/pic/garmin/garmin5.jpg"&gt;&lt;img alt="🇫🇮" src="https://developer.run/pic/garmin/garmin5-200x.png" /&gt;&lt;/a&gt;
&lt;a href="https://developer.run/pic/garmin/garmin4.jpg"&gt;&lt;img alt="🫟" src="https://developer.run/pic/garmin/garmin4-200x.png" /&gt;&lt;/a&gt;
&lt;a href="https://developer.run/pic/garmin/garmin6.jpg"&gt;&lt;img alt="🏴‍☠️" src="https://developer.run/pic/garmin/garmin6-200x.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I also like to use different straps all the time, so that when I look at watch, it looks fresh and doesn't get too mundane. &lt;/p&gt;
&lt;p&gt;&lt;a href="https://developer.run/pic/garmin/garmin7.jpg"&gt;&lt;img alt="Bonus photo with fatbear iPhone case" src="https://developer.run/pic/garmin/garmin7-200x.png" /&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="/search?q=%22%23PhotoOfTheDay%22" rel="tag"&gt;#PhotoOfTheDay&lt;/a&gt; &lt;a href="/search?q=%22%23survival%22" rel="tag"&gt;#survival&lt;/a&gt; &lt;a href="/search?q=%22%23gear%22" rel="tag"&gt;#gear&lt;/a&gt; &lt;a href="/search?q=%22%23prepper%22" rel="tag"&gt;#prepper&lt;/a&gt; &lt;a href="/search?q=%22%23MilitarishAesthetic%22" rel="tag"&gt;#MilitarishAesthetic&lt;/a&gt; &lt;a href="/search?q=%22%23travel%22" rel="tag"&gt;#travel&lt;/a&gt;&lt;/p&gt;</description><author>Developer Run</author><pubDate>Sat, 07 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://developer.run/79</guid></item><item><title>Indie social sign-in could go mainstream</title><link>https://blog.erlend.sh/indie-social-sign-in-could-go-mainstream?pk_campaign=rss-feed</link><description/><author>Open Indie</author><pubDate>Fri, 06 Sep 2024 20:01:42 GMT</pubDate><guid isPermaLink="true">https://blog.erlend.sh/indie-social-sign-in-could-go-mainstream?pk_campaign=rss-feed</guid></item><item><title>Hello ionic</title><link>https://whackylabs.com/js/ionic/ios/android/2024/09/06/hello-ionic/</link><description>&lt;p&gt;In the next series of how javascript is slowly taking over the tech let’s try Ionic.&lt;/p&gt;

&lt;p&gt;&lt;img alt="Javascript is taking over" src="https://i.imgflip.com/92se3o.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;On their home page Ionic Framework calls themselves as &lt;strong&gt;The Cross-Platform App Development Leader&lt;/strong&gt;. Lets find out the truth in that.&lt;/p&gt;

&lt;p&gt;I remember using Cordova back in 2010 and honestly the mobile landscape was very different back then. From what I’ve read so far, ionic was built on top of Cordova and angular.js but there is also an out-of-box template for react, which is what I’m probably going to use today. So let’s go!&lt;/p&gt;

&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;
&lt;p&gt;First start the ionic cli.&lt;/p&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;npm install -g @ionic/cli native-run cordova-res&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Running &lt;code class="language-plaintext highlighter-rouge"&gt;ionic start --list&lt;/code&gt; reveals the following list of templates:&lt;/p&gt;
&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;Starters for @ionic/react (--type=react)

name         | description
--------------------------------------------------------------------------------------
blank        | A blank starter project
list         | A starting project with a list
my-first-app | A template for the "Build Your First App" tutorial
sidemenu     | A starting project with a side menu with navigation in the content area
tabs         | A starting project with a simple tabbed interface
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;So I’m obviously going to use the &lt;code class="language-plaintext highlighter-rouge"&gt;blank&lt;/code&gt; template:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;ionic start photoapp blank --type=react --capacitor&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And then few more installs:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;npm install @capacitor/camera @capacitor/preferences @capacitor/filesystem&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And then a few more:&lt;/p&gt;

&lt;p&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;npm install @ionic/pwa-elements&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;And then finally &lt;em&gt;a few minutes later&lt;/em&gt; running &lt;code class="language-plaintext highlighter-rouge"&gt;ionic serve&lt;/code&gt; starts the good old familiar vite react app on the browser. Good, but I thought we are building a mobile app. So let’s fix that.&lt;/p&gt;

&lt;p&gt;First we need to build the project.&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;ionic build
ionic cap add ios
ionic cap add android
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Later in development if we wish to copy the web project into iOS, we need to run the &lt;code class="language-plaintext highlighter-rouge"&gt;ionic cap copy&lt;/code&gt; command.
And need to sync after adding plugins we need to run the &lt;code class="language-plaintext highlighter-rouge"&gt;ionic cap sync&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;And then finally to run the project for iOS we need to run the &lt;code class="language-plaintext highlighter-rouge"&gt;ionic cap open ios&lt;/code&gt; command. This would open the Xcode project from where we can actually run the app in our favorite simulator.&lt;/p&gt;

&lt;p&gt;&lt;img alt="setup" src="https://whackylabs.com/assets/hello-ionic/01-setup.png" /&gt;&lt;/p&gt;

&lt;h3 id="drawing-ui"&gt;Drawing UI&lt;/h3&gt;
&lt;p&gt;Very nice. Now let’s switch gears to rendering. Ionic provides a nice list of UI components to avoid lazy devs like me from writing the css. To make a 2 column grid of photos we can make use of &lt;code class="language-plaintext highlighter-rouge"&gt;IonGrid&lt;/code&gt;. &lt;code class="language-plaintext highlighter-rouge"&gt;IonGrid&lt;/code&gt; is made up of many &lt;code class="language-plaintext highlighter-rouge"&gt;IonRow&lt;/code&gt;, and &lt;code class="language-plaintext highlighter-rouge"&gt;IonRow&lt;/code&gt; is made up of many &lt;code class="language-plaintext highlighter-rouge"&gt;IonCol&lt;/code&gt; items.&lt;/p&gt;

&lt;p&gt;So make a 2 column grid we can design our grid as:&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PhotoTileProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Photo&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PhotoTile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PhotoTileProps&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonCol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonImg&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonCol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PhotoRowProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Photo&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Photo&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PhotoRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PhotoRowProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonRow&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PhotoTile&lt;/span&gt; &lt;span class="na"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PhotoTile&lt;/span&gt; &lt;span class="na"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonRow&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PhotoList&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;photoList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPhotoList&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PhotoRowProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://jsonplaceholder.typicode.com/photos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;PhotoRowProps&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;element&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
      &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PhotoRowProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;row&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;setPhotoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonGrid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photoList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PhotoRow&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;left&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;left&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;right&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;right&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonGrid&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="grid" src="https://whackylabs.com/assets/hello-ionic/02-grid.png" /&gt;&lt;/p&gt;

&lt;p&gt;This should work for even numbered elements, but for odd numbered elements our grid would look weird, since the last element would occupy the entire width. I can reproduce this bug by only rendering 3 elements&lt;/p&gt;

&lt;p&gt;&lt;img alt="grid broken" src="https://whackylabs.com/assets/hello-ionic/03-grid-odd.png" /&gt;&lt;/p&gt;

&lt;p&gt;An easy fix is to always draw the &lt;code class="language-plaintext highlighter-rouge"&gt;IonCol&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PhotoTile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PhotoTileProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonCol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonImg&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonCol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="grid fix" src="https://whackylabs.com/assets/hello-ionic/04-grid-odd-fix.png" /&gt;&lt;/p&gt;

&lt;p&gt;For details page we can build a UI similarly.:&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PhotoDetailProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PhotoDetail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PhotoDetailProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPhoto&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Photo&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://jsonplaceholder.typicode.com/photos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;photo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;setPhoto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonSpinner&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PhotoTile&lt;/span&gt;
      &lt;span class="na"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="details" src="https://whackylabs.com/assets/hello-ionic/05-details.png" /&gt;&lt;/p&gt;

&lt;h3 id="navigation"&gt;Navigation&lt;/h3&gt;
&lt;p&gt;The boilerplate app already provides a router which looks very familiar because, no surprise, it’s a wrapper around &lt;code class="language-plaintext highlighter-rouge"&gt;react-router&lt;/code&gt;.&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonApp&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonReactRouter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonRouterOutlet&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;exact&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/home"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;exact&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Redirect&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/home"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonRouterOutlet&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonReactRouter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonApp&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And to extend the router to show a details page with param we can add the usual react-router styled &lt;code class="language-plaintext highlighter-rouge"&gt;Route&lt;/code&gt;. So for our photo app our routes would look like:&lt;/p&gt;
&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonApp&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonReactRouter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonRouterOutlet&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/home"&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Home&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Route&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/details/:id"&lt;/span&gt; &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Details&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Redirect&lt;/span&gt; &lt;span class="na"&gt;exact&lt;/span&gt; &lt;span class="na"&gt;from&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"/home"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonRouterOutlet&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonReactRouter&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonApp&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To parse the path argument we can use the &lt;code class="language-plaintext highlighter-rouge"&gt;RouteComponentProps&lt;/code&gt;.&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserDetailPageProps&lt;/span&gt;
  &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;RouteComponentProps&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Details&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UserDetailPageProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonPage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonToolbar&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonTitle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Photo &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonTitle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonToolbar&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonHeader&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonContent&lt;/span&gt; &lt;span class="na"&gt;fullscreen&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PhotoDetail&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonContent&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonPage&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now to navigate between &lt;code class="language-plaintext highlighter-rouge"&gt;Home&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;Details&lt;/code&gt; we can use the &lt;code class="language-plaintext highlighter-rouge"&gt;routerLink&lt;/code&gt; that is provided with a lot of Ionic components, like the &lt;code class="language-plaintext highlighter-rouge"&gt;IonCard&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PhotoTileProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;routerLink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;photoUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;photoTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;createPhotoTileProps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Photo&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;PhotoTileProps&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
     &lt;span class="na"&gt;routerLink&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`/details/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;photoUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
     &lt;span class="na"&gt;photoTitle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
   &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PhotoTile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PhotoTileProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonCard&lt;/span&gt; &lt;span class="na"&gt;routerLink&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;routerLink&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonCol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonImg&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photoUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photoTitle&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonLabel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photoTitle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonLabel&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonCol&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonCard&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And there you have it. The photo app with Ionic framework!&lt;/p&gt;

&lt;p&gt;As a final step, let’s build and run the app using Xcode one more time. So running &lt;code class="language-plaintext highlighter-rouge"&gt;ionic build &amp;amp;&amp;amp; ionic cap copy&lt;/code&gt; one more time. And then running the app from Xcode. Viola!&lt;/p&gt;

&lt;p&gt;&lt;img alt="iOS-home" src="https://whackylabs.com/assets/hello-ionic/06-ios-home.png" /&gt;
&lt;img alt="iOS-details" src="https://whackylabs.com/assets/hello-ionic/07-ios-details.png" /&gt;&lt;/p&gt;

&lt;p&gt;The details screen seems to be missing the back button. The fix is actually very simple, just add the &lt;code class="language-plaintext highlighter-rouge"&gt;IonBackButton&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonToolbar&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonButtons&lt;/span&gt; &lt;span class="na"&gt;slot&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonBackButton&lt;/span&gt; &lt;span class="na"&gt;defaultHref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"#"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonBackButton&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonButtons&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;IonTitle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Photo &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonTitle&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;IonToolbar&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="iOS-details-back" src="https://whackylabs.com/assets/hello-ionic/08-ios-details-back.png" /&gt;&lt;/p&gt;

&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I think Ionic is pretty solid framework for web developers. You can almost always tell that this is web UI, and I don’t think ionic is trying to hide that fact. But like with every thing software there are always trade-offs and the fact that Ionic is not limiting the web devs from using 100% of their expertise can be a huge win.&lt;/p&gt;

&lt;p&gt;Like always the code from this experiment is available at &lt;a href="https://github.com/chunkyguy/PhotoApp/tree/master/ionic"&gt;https://github.com/chunkyguy/PhotoApp&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="references"&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://ionicframework.com/"&gt;Ionic Framework&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ionicframework.com/docs/react/your-first-app"&gt;Your First Ionic App: React&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://ionicframework.com/docs/components"&gt;UI Components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Fri, 06 Sep 2024 19:13:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/js/ionic/ios/android/2024/09/06/hello-ionic/</guid></item><item><title>Новости международной политики</title><link>https://avodonosov.blogspot.com/2015/10/blog-post.html</link><description>&lt;div style="text-align: center;"&gt;
  &lt;a href="https://youtu.be/pyqD23zX0YI?t=3817" target="_blank"&gt;
    &lt;img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi-p0ya7ThOYYkXv68BKZeI7qSGwQZPFsH9MNqKM8lp2L1Zav7QmQ_w7Z7XdECWbmiNG6hGNauKu9kQlSPnTceOVCVOHXHZaGCn8BtLLQGrlTppLRopkiMHmS_L7jTcVJp0AN6E4fAYjfD_/s1600/international-politics-news.png" /&gt;
  &lt;/a&gt;
  &lt;br /&gt;(Предлагаю смотреть 40 секунд начиная с этого места.)
&lt;/div&gt;</description><author>blog</author><pubDate>Fri, 06 Sep 2024 07:36:13 GMT</pubDate><guid isPermaLink="true">https://avodonosov.blogspot.com/2015/10/blog-post.html</guid></item><item><title/><link>https://avodonosov.blogspot.com/2016/03/blog-post.html</link><description>&lt;br /&gt;
Мчится тройка удалая,&lt;br /&gt;
Тройка сталинская&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;</description><author>blog</author><pubDate>Fri, 06 Sep 2024 07:25:28 GMT</pubDate><guid isPermaLink="true">https://avodonosov.blogspot.com/2016/03/blog-post.html</guid></item><item><title>I encourage you to write a blog</title><link>https://ounapuu.ee/posts/2024/09/06/blog/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/09/06/blog/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;It&amp;rsquo;s been over 4 years since &lt;a href="https://ounapuu.ee/posts/2020/07/23/the-little-wifi-ap-that-could/"&gt;my first post&lt;/a&gt; on this blog.&lt;/p&gt;
&lt;p&gt;During those 4 years I&amp;rsquo;ve written over 90 posts, received over 1 million clicks,
a dozen legitimate reader e-mails and thousands of spam e-mails.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;And I love it!&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve found that writing can be very fulfilling and I encourage you to at least
give it a try.&lt;/p&gt;
&lt;p&gt;This post covers the reasons why I write, how I write and some tips on how you can get started writing one yourself.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Blogging is so &lt;del&gt;2004&lt;/del&gt; 2024.&lt;/em&gt;&lt;/p&gt;
&lt;h3 id="why-i-write"&gt;Why I write&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m a software developer by trade. However, some might find it surprising that
I actually write very little code in my free time.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/06/blog/media/github.png"&gt;
        &lt;img alt="My GitHub activity." height="213" src="https://ounapuu.ee/posts/2024/09/06/blog/media/github_hu_107d1ead31d91e23.png" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      My GitHub activity.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Instead, I play around with computer hardware, self-host some services on my
machines, and make stupid experiments that often result in interesting findings.&lt;/p&gt;
&lt;p&gt;Most of that work had been invisible, until I started writing about them on my blog.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I write because other people find my posts useful.&lt;/strong&gt; I&amp;rsquo;ve learned that
some of my posts have been really useful to people who&amp;rsquo;ve faced similar problems
in the past or who want to learn more about a relatively obscure piece of hardware.
Tools like &lt;a href="https://search.marginalia.nu/site/ounapuu.ee?view=links"&gt;Marginalia search&lt;/a&gt;,
&lt;a href="https://search.google.com/search-console/"&gt;Google Search Console&lt;/a&gt; and &lt;code&gt;goaccess&lt;/code&gt;
have been great sources for finding where my content has been referenced.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve found my content linked in all sorts of places:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://wiki.gentoo.org/wiki/Lenovo_Thinkpad_T430"&gt;Gentoo wiki entry for the Lenovo ThinkPad T430&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://libreboot.org/docs/install/"&gt;Libreboot installation instructions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/ZQX4FLrm9ac?t=288"&gt;a YouTube video with almost a million views&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/MaOORTLJyzk?t=505"&gt;a YouTube video with ~1400 views&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;it&amp;rsquo;s properly sourced, I love it!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.marbu.eu/posts/2023-08-02-btrfs-backup/"&gt;other small blogs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://changelog.com/news/66"&gt;link aggregators&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;various forums, including &lt;a href="https://forums.thinkpads.com/viewtopic.php?p=875876&amp;amp;sid=5e0633b0f018cce81294cfbbac0b46b2#p875876"&gt;the ThinkPad one&lt;/a&gt; and &lt;a href="https://forum.qubes-os.org/t/convert-x230-into-a-certified-laptop/12341/3"&gt;QubesOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.reddit.com/r/thinkpad/comments/xv9qp3/comment/ir20gh1/"&gt;Reddit&lt;/a&gt;, even &lt;a href="https://www.reddit.com/r/AMDLaptops/comments/11indpx/comment/jb08s1v/"&gt;the really obscure stuff&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hackaday.com/2024/06/13/raspberry-pi-saves-printer-from-junk-pile/"&gt;even Hackaday!&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;also got &lt;a href="https://openprinting.github.io/OpenPrinting-News-June-2024/#raspberry-pi-saves-old-printers"&gt;a mention from the OpenPrinting project leader!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;same post was also featured on &lt;a href="https://www.xda-developers.com/raspberry-pi-unsupported-printer-on-windows/"&gt;XDA Developers&lt;/a&gt; and &lt;a href="https://www.hackster.io/news/herman-ounapuu-brings-an-abandoned-printer-back-from-the-brink-with-a-raspberry-pi-90701ccb1ab9"&gt;hackster.io&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;there was even a case
where &lt;a href="https://www.golem.de/news/notebook-warum-ich-jetzt-wieder-ein-thinkpad-von-2012-nutze-2209-166195.html"&gt;my post was translated into German and published on golem.de&lt;/a&gt;,
with my written permission.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Seeing my work out there helping people gives me an immense sense of accomplishment
and motivates me to write about the challenges I face. It really does mean a lot
to me. It&amp;rsquo;s not quite on the level of &lt;a href="https://github.com/geerlingguy"&gt;Jeff Geerling and the work that he has published,&lt;/a&gt;
but perhaps I&amp;rsquo;ll get there one day.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/06/blog/media/nfscars.png"&gt;
        &lt;img alt="People on Reddit finding my archival efforts to be useful. I love to see this!" height="620" src="https://ounapuu.ee/posts/2024/09/06/blog/media/nfscars_hu_dbc7c2950e58155.png" style="width: auto; height: auto; border-radius: 8px;" width="772" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      People on Reddit finding my archival efforts to be useful. I love to see this!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;I also often end up looking up my own posts because of some technical details
that I&amp;rsquo;ve written down, or to share a post in a discussion because of its
relevance. The one
about &lt;a href="https://ounapuu.ee/posts/2022/09/26/minimum-viable-fan-control-script/"&gt;the minimum viable fan control script&lt;/a&gt;
has been very handy, for example.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/06/blog/media/google-search-zfs.png"&gt;
        &lt;img alt="This post has been performing well consistently for years!" height="439" src="https://ounapuu.ee/posts/2024/09/06/blog/media/google-search-zfs_hu_49a90d4489faff62.png" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      This post has been performing well consistently for years!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Quite often I end up finding solutions in other indie blogs myself. There&amp;rsquo;s something
special and authentic about those, and they often go more in-depth than places like
StackOverflow. Ads and inappropriate sponsorships are also rare on those.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I write because I forget things.&lt;/strong&gt; My blog serves as a public diary of sorts,
and it has been extremely helpful in reminding me about all the fun things I&amp;rsquo;ve
worked on, or experiences I&amp;rsquo;ve had. I sometimes revisit my older posts and
I still find it surprising how much I&amp;rsquo;ve managed to do, or what I&amp;rsquo;ve worked on.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I write because I can do what I want.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;No &lt;a href="https://ludic.mataroa.blog/blog/synergy-greg/"&gt;Synergy Greg.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;No managers.&lt;/p&gt;
&lt;p&gt;No meetings.&lt;/p&gt;
&lt;p&gt;No quarterly plannings.&lt;/p&gt;
&lt;p&gt;No unpleasant colleagues.&lt;/p&gt;
&lt;p&gt;No conflicts.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s really nice.&lt;/p&gt;
&lt;h3 id="rules-of-engagement"&gt;Rules of engagement&lt;/h3&gt;
&lt;p&gt;Whenever I write something, I try to follow a set of principles. These have been
influenced by my own ideological views and content creators that I look up to.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Write content that I would like to read myself.&lt;/strong&gt; I love stumbling upon well-written
posts covering topics that the author is passionate about. These posts have a
special energy in them, which is very infectious and motivates me to put more
effort into my own writing.&lt;/p&gt;
&lt;p&gt;If you want to know what I like to read, then check out the list of good posts &lt;a href="https://ounapuu.ee/misc/good-reads/"&gt;here&lt;/a&gt;,
and anything else that&amp;rsquo;s cool goes &lt;a href="https://ounapuu.ee/misc/cool-projects/"&gt;here&lt;/a&gt; and &lt;a href="https://ounapuu.ee/misc/cool-links/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Straight to the point.&lt;/strong&gt; I try to avoid unnecessary word salad whenever
possible and try to get my point across in a clear and concise way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Make the content accessible.&lt;/strong&gt; The person reading my blog might be a seasoned
developer, or someone just starting out in IT. It&amp;rsquo;s a balancing act and I&amp;rsquo;m
unlikely to ever get it just right, but I try to remind myself of what I knew
back when I was just starting out and try to address that person in my writing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Listen to the audience.&lt;/strong&gt; If the readers demand to see the cute cats you mentioned
in a post, &lt;a href="https://ounapuu.ee/posts/2023/12/19/spicy-usb-adapter/#2023-12-19-update"&gt;then you better deliver.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep performance in mind.&lt;/strong&gt; I do my best to keep the page size down to a reasonable
size. This results in my own bandwidth requirements being lower, allowing me to
serve more traffic over the same connection. It is also helpful to readers who
are on a slower network connection or are running slower
hardware.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://infrequently.org/series/performance-inequality"&gt;The Performance Inequality Gap&lt;/a&gt;
series has inspired me a lot in this area. I&amp;rsquo;m not going to do every
micro-optimization that tools
like &lt;a href="https://developer.chrome.com/docs/lighthouse/performance/performance-scoring"&gt;Lighthouse&lt;/a&gt;
complain about, but I will do what I consider reasonable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No ads.&lt;/strong&gt; A couple of dollars a month is not worth compromising the privacy
of my readers. Relevant and up-front collaborations with manufacturers in the
style of Level1Techs and Jeff Geerling are still OK in my view.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No shady sponsors.&lt;/strong&gt; Most sponsorship offers that I get involve gambling, and
gambling but with extra steps (crypto-&amp;ldquo;currencies&amp;rdquo;). Anyone working in an area
that exploits human psychology to make obscene amounts of money (at the cost of
ruining actual human lives) should really reconsider their life choices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No so-called &amp;ldquo;guest posts&amp;rdquo; or backlinks.&lt;/strong&gt; I have received hundreds of these SEO
spam e-mails at this point, and the pattern is the same:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;hey ounapuu.ee, we really love your site about &lt;code&gt;$NOT_WHAT_I_ACTUALLY_WRITE_ABOUT&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;are you ok with linking to &lt;code&gt;$SHADY_SITE&lt;/code&gt; or a guest post? what&amp;rsquo;s your asking price?&lt;/p&gt;
&lt;p&gt;Latifi Hamilton, SEO Spam Corp. Ltd&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I am 100% convinced that SEO optimization in this form is an absolutely
useless &amp;ldquo;job&amp;rdquo; and a net negative to the world. Please stop.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;No generative AI garbage.&lt;/strong&gt; It conflicts with the reasons why I write a blog
in the first place. I&amp;rsquo;ve tried things like GPT-3 to see what it regurgitates, and
it has been the most unoriginal, fluffy drivel that gets all the important details
wrong.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also seen the same-looking generative AI imagery in lots of blog posts
and presentations, it&amp;rsquo;s really off-putting and shows a lack of care and effort
by the author, even if the rest of the content is solid.&lt;/p&gt;
&lt;h3 id="getting-started"&gt;Getting started&lt;/h3&gt;
&lt;p&gt;So you&amp;rsquo;ve finally decided to start your own blog. Great!
Here are the steps that I believe make sense to someone just starting up.&lt;/p&gt;
&lt;h4 id="why-are-you-writing"&gt;Why are &lt;em&gt;you&lt;/em&gt; writing?&lt;/h4&gt;
&lt;p&gt;This is the most important step. Don&amp;rsquo;t skip this.&lt;/p&gt;
&lt;p&gt;A lot of techy people (myself included) get carried away with the technical details before writing the first post.
It&amp;rsquo;s understandable, the technology aspect can be very exciting on its own. However, focusing on the
blog setup itself distracts from the core of your blog: the content.&lt;/p&gt;
&lt;p&gt;Start by figuring out the reasons why you want to write in the first place.&lt;/p&gt;
&lt;p&gt;It could be anything:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;you have a fun hobby that deserves more coverage&lt;/li&gt;
&lt;li&gt;you find that there are products in your niche that don&amp;rsquo;t have any proper reviews&lt;/li&gt;
&lt;li&gt;you want to share some stories and lessons learned from your professional career&lt;/li&gt;
&lt;li&gt;you want to create content on a platform that you have full control over&lt;/li&gt;
&lt;li&gt;you just want to share your work with the world&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now that you&amp;rsquo;ve got that figured out, I recommend writing up your first draft, even before
you know where you&amp;rsquo;re going to host it. Any plain-text file will be fine for
this purpose, and you&amp;rsquo;ll most definitely rewrite most of it and touch it up
once you know where you&amp;rsquo;re going to host your blog anyway.&lt;/p&gt;
&lt;h4 id="find-a-place-to-host-your-blog"&gt;Find a place to host your blog&lt;/h4&gt;
&lt;p&gt;You have your goals in place and you have the draft for your first post. Great!&lt;/p&gt;
&lt;p&gt;There are a lot of options out there for hosting your blog, each with their
own strengths and weaknesses. You should pick the option that fits your needs
the best.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Indie blogging platforms&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Examples: &lt;a href="https://mataroa.blog/"&gt;Mataroa&lt;/a&gt;, &lt;a href="https://bearblog.dev/"&gt;Bear Blog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;These come in all sorts of flavours and offer different functionalities for free or a
small nominal fee. Usually run by one or more people as a passion project or side-gig.
Easy to start out with, and the good ones offer easy ways to take your posts with
you if you ever decide to move to another platform.&lt;/p&gt;
&lt;p&gt;If I didn&amp;rsquo;t already host my blog on my own server, then I&amp;rsquo;d probably use one of
these types of services.&lt;/p&gt;
&lt;p&gt;Fun fact: &lt;a href="https://bearblog.dev/"&gt;Bear Blog&lt;/a&gt; is run by another Herman!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Big blogging platforms&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Examples: &lt;a href="https://medium.com/"&gt;Medium&lt;/a&gt;, &lt;a href="https://substack.com/"&gt;Substack&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Quite popular and likely have good tooling for writing content easily, but you
usually have less control over your work and you are at
the whims of the platform. If they ever decide to completely change their
business model and start charging you much higher prices for hosting your work there, then
you better hope that they have an easy way to export your content.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dynamic site generators&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Examples: WordPress, Drupal, Joomla&lt;/p&gt;
&lt;p&gt;You can host these on your own server, or pay someone else to do the hosting
for you. Can be easily customized and should be quite beginner-friendly for
content creation. Pages are created on the fly, so these are often not the most performant option. Can easily fall over under
high traffic if the setup is not optimized and your post gets linked on a popular platform (&amp;ldquo;hug of death&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;If you ever have to pick one of these types of services, then you must make sure
to keep it up to date. WordPress vulnerabilities can be exploited &lt;strong&gt;within hours&lt;/strong&gt; after a vulnerability disclosure.&lt;/p&gt;
&lt;p&gt;The performance and security considerations are the main reason why I avoid these
options.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Static site generators&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Examples: &lt;a href="https://gohugo.io/"&gt;Hugo&lt;/a&gt;, &lt;a href="https://jekyllrb.com/"&gt;Jekyll&lt;/a&gt; and many, many more&lt;/p&gt;
&lt;p&gt;With static site generators, you build the site once and copy the contents to
your web server. This comes with great performance benefits as serving static
files on a web server is really darn fast. Even the cheapest virtual private
server at any cloud provider can likely handle more than a gigabit of
SSL-encrypted traffic with this option.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Security is also less of an issue. As long as your web server is up to date, then
you don&amp;rsquo;t have much to worry about, just make sure to not accidentally push any sensitive
files and information along with your blog.&lt;/p&gt;
&lt;p&gt;These benefits come with a few trade-offs. The content is written in Markdown and while you can embed
code examples and images, it&amp;rsquo;s not as user-friendly as other platforms. Adding dynamic content and comment sections
is not included by default.&lt;/p&gt;
&lt;p&gt;This is the option I&amp;rsquo;ve chosen. The site is built with Hugo, the web server part
is handled by Fedora Server, Docker
and &lt;a href="https://docs.linuxserver.io/general/swag/"&gt;this container&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://pages.github.com/"&gt;It&amp;rsquo;s also possible to host your static site on GitHub&lt;/a&gt;, if you don&amp;rsquo;t want to
deal with the hassle of having a web server around. The downside of this approach is that
GitHub is quite often down, and their downtime is now your downtime.&lt;/p&gt;
&lt;h4 id="just-do-it"&gt;Just do it.&lt;/h4&gt;
&lt;p&gt;Go ahead, write that first post! &lt;a href="https://ounapuu.ee/posts/2020/07/23/the-little-wifi-ap-that-could/"&gt;Here&amp;rsquo;s mine for reference,&lt;/a&gt; not great, not terrible.&lt;/p&gt;
&lt;p&gt;Don&amp;rsquo;t worry, even I started a blog and abandoned it &lt;a href="https://web.archive.org/web/20190325122634/https://blog.ounapuu.ee/"&gt;on my first attempt.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Sometimes I start writing when I don&amp;rsquo;t feel like it. If there&amp;rsquo;s a 20 minute time window, I can write the skeleton
for a new post, or finalize a work-in-progress one. Once I get past the hurdle of starting, I&amp;rsquo;m in the zone.
Some of my best posts have been written under these conditions.&lt;/p&gt;
&lt;p&gt;Daniel Stenberg had &lt;a href="https://daniel.haxx.se/blog/2024/02/06/fosdem-2024-you-too-could-have-made-curl/"&gt;a talk at FOSDEM 2024&lt;/a&gt;
that incidentally covered similar time management techniques that help him get work done on &lt;code&gt;curl&lt;/code&gt;, I highly recommend
giving it a listen.&lt;/p&gt;
&lt;h4 id="build-a-habit"&gt;Build a habit&lt;/h4&gt;
&lt;p&gt;I have a goal to write at least two posts per month and I&amp;rsquo;ve mostly stuck to it, with a few exceptions.
Internal deadlines help me actually get my thoughts and work out there in a somewhat timely manner.&lt;/p&gt;
&lt;p&gt;Having this cadence also helps in situation where I expect a busy schedule, as I can try to finish a few posts in advance.&lt;/p&gt;
&lt;p&gt;A post can take anywhere from 1 to 4 hours in my experience. Some can take a few days, such as &lt;a href="https://ounapuu.ee/posts/2024/02/12/fosdem-2024/"&gt;my FOSDEM 2024 post.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;One post per month can be a good goal for a first time writer.&lt;/p&gt;
&lt;h4 id="tooling"&gt;Tooling&lt;/h4&gt;
&lt;p&gt;You don&amp;rsquo;t need anything special for writing. Use whatever works for you. If it supports spell-check, then that&amp;rsquo;s even better.&lt;/p&gt;
&lt;p&gt;I write my posts in Markdown and the best tool I&amp;rsquo;ve found for that happens to be IntelliJ IDEA.
I already use it for development work and its Markdown support is good. That&amp;rsquo;s it.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve used tools like MarkText &lt;a href="https://ounapuu.ee/posts/2024/02/13/oops/"&gt;but it went really poorly the last time.&lt;/a&gt;
Other text editors haven&amp;rsquo;t worked for me for various reasons.&lt;/p&gt;
&lt;h4 id="optional-buy-a-domain-name"&gt;Optional: buy a domain name&lt;/h4&gt;
&lt;p&gt;You may want to get a domain name for your blog. Depending on your setup, it can make transferring your
blog to a different provider or service much easier, simply point your domain to a new IP address!&lt;/p&gt;
&lt;p&gt;This does add a yearly cost that might not be acceptable for everyone.&lt;/p&gt;
&lt;h4 id="optional-use-a-completely-separate-contact-e-mail"&gt;Optional: use a completely separate contact e-mail&lt;/h4&gt;
&lt;p&gt;If you add a contact e-mail to your blog, then you will get spam, guaranteed.&lt;/p&gt;
&lt;p&gt;I use a completely different e-mail because of this, it helps me easily see where the spammers got my e-mail.
I also find it funny that every time a spammer e-mails me, they&amp;rsquo;re blatantly lying without even trying. You see, my e-mail
is &lt;code&gt;ihavesomethoughtsonyourblog@ounapuu.ee&lt;/code&gt; and it&amp;rsquo;s rarely the case that they actually have thoughts on my blog.&lt;/p&gt;
&lt;p&gt;If you have a domain name and a decent e-mail service provider, then adding additional e-mails should be quite easy.&lt;/p&gt;
&lt;h4 id="optional-self-host-at-home"&gt;Optional: self-host at home&lt;/h4&gt;
&lt;p&gt;A small blog is a great first-time project for someone that wants to get into self-hosting and building a homelab.&lt;/p&gt;
&lt;p&gt;All you need are ways to open port 80 and 443 for HTTP and HTTPS.&lt;/p&gt;
&lt;p&gt;A residential connection will likely have a dynamic IP address. It might be possible to pay extra to have a static
IP address. It&amp;rsquo;s also possible to sign up with a dynamic DNS provider that handle IP address changes for you.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve gone one step further and written a DNS updater Python script against my domain registrar. If you can read this
text, then it means that the script works!&lt;/p&gt;
&lt;h3 id="how-i-write"&gt;How I write&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;m not going to tell you how to write because I can barely manage to do it myself, and there are smarter people out
there from whom you can learn that.&lt;/p&gt;
&lt;p&gt;What I &lt;em&gt;can&lt;/em&gt; do is to share my writing process in the hopes that there are a few tech tips that you find useful.&lt;/p&gt;
&lt;h4 id="writing"&gt;Writing&lt;/h4&gt;
&lt;p&gt;I have a list of ideas that I have written down. When I get the opportunity or
inspiration to write, I look at the list and pick something from it.&lt;/p&gt;
&lt;p&gt;Every post starts with a draft title and a bulleted list of points to cover in a post. That list is like a table of
contents for the post, and then I start expanding on them in writing.&lt;/p&gt;
&lt;p&gt;Pictures are usually added after the first draft is done.&lt;/p&gt;
&lt;h4 id="sharing-my-work"&gt;Sharing my work&lt;/h4&gt;
&lt;p&gt;I believe in &lt;a href="https://indieweb.org/POSSE"&gt;POSSE&lt;/a&gt;: &lt;em&gt;Publish (on your) Own Site, Syndicate Elsewhere&lt;/em&gt;.
I have full control over my blog and can link to it on all sorts of platforms. Screw &amp;ldquo;big tech&amp;rdquo;!&lt;/p&gt;
&lt;p&gt;Sharing my work has been tricky, however. I can always share it in platforms like LinkedIn,
but links like that get down ranked compared  to &amp;ldquo;native&amp;rdquo; posts on the platform itself.&lt;/p&gt;
&lt;p&gt;Link aggregators, such as Hacker News and Reddit, discourage or forbid sharing your own content. I have done it, but only
with content that I think is relevant to the communities. There&amp;rsquo;s a reason these rules are in place, simply check
&lt;a href="https://news.ycombinator.com/newest"&gt;the newest posts on Hacker News&lt;/a&gt; to understand why.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t recommend this type of behaviour, but if you have a good understanding of the community and what content they
like to see, then it can be a good way to get some eyes on your content.&lt;/p&gt;
&lt;p&gt;Once you get a following, you don&amp;rsquo;t have to worry much about sharing
your work as your readers will probably do it for you.&lt;/p&gt;
&lt;h4 id="how-readers-keep-up-with-my-blog"&gt;How readers keep up with my blog&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;ve seen two ways that readers follow blogs: mailing lists and RSS feeds.&lt;/p&gt;
&lt;p&gt;Readers can follow my blog using &lt;a href="https://ounapuu.ee/index.xml"&gt;the RSS feed.&lt;/a&gt;
For those that are unfamiliar with RSS, I link to &lt;a href="https://aboutfeeds.com/"&gt;aboutfeeds.com&lt;/a&gt; at the bottom of my posts.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s harder to track the number of subscribers with an RSS feed, but I believe it&amp;rsquo;s the approach that best preserves
reader privacy.&lt;/p&gt;
&lt;p&gt;Some feed reader services, such as Feedly, Inoreader and Feedbin, have a neat little feature where
they actually send the number of subscribers in the user agent when making a request towards your RSS feed, and that user agent
is visible in your web server logs:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;127.0.0.1 - - [21/Aug/2024:10:40:19 +0300] &amp;quot;GET /index.xml HTTP/1.1&amp;quot; 304 0 &amp;quot;-&amp;quot; &amp;quot;Feedly/1.0 (+http://www.feedly.com/fetcher.html; 72 subscribers; )&amp;quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Mailing lists are also quite common for blogs, but I never found a good mailing list provider that costs a reasonable
amount of money, so I never started one. I&amp;rsquo;m also not going to manually maintain one or build a solution myself.&lt;/p&gt;
&lt;h3 id="analytics"&gt;Analytics&lt;/h3&gt;
&lt;p&gt;My main analytics tool is &lt;code&gt;goaccess&lt;/code&gt;, a handy terminal UI tool that reads &lt;code&gt;nginx&lt;/code&gt; web server logs and outputs basic
statistics. This is just enough information for getting a basic understanding about how well my posts are doing.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;goaccess&lt;/code&gt; can also track incoming requests live, which is very cool to observe when your post has gained traction on
Hacker News.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/06/blog/media/goaccess.png"&gt;
        &lt;img alt="Small glimpse of what goaccess can show." height="568" src="https://ounapuu.ee/posts/2024/09/06/blog/media/goaccess_hu_67f0c669b9447611.png" style="width: auto; height: auto; border-radius: 8px;" width="709" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Small glimpse of what goaccess can show.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;I also check in on Google Search Console from time to time. It&amp;rsquo;s been quite handy for understanding how well my posts
are doing in Google Search.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/06/blog/media/google-search-console.png"&gt;
        &lt;img alt="Example of Google Search Console results." height="353" src="https://ounapuu.ee/posts/2024/09/06/blog/media/google-search-console_hu_3166493a5b1d891e.png" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Example of Google Search Console results.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;I can also get an understanding how my content is doing elsewhere. I don&amp;rsquo;t have Google Analytics on my page, but I can
still see when my posts have gained traction on Hacker News or elsewhere.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/06/blog/media/google-search-console-discover.png"&gt;
        &lt;img alt="I guess they track popular HN posts." height="353" src="https://ounapuu.ee/posts/2024/09/06/blog/media/google-search-console-discover_hu_9b4eec8f2df040bd.png" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      I guess they track popular HN posts.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;h3 id="observations-and-tech-tips"&gt;Observations and tech tips&lt;/h3&gt;
&lt;p&gt;My years of running a blog have resulted in some potentially interesting tidbits and tips that I can share.&lt;/p&gt;
&lt;h4 id="hacker-news"&gt;Hacker News&lt;/h4&gt;
&lt;p&gt;&lt;a href="https://news.ycombinator.com/news"&gt;Hacker News&lt;/a&gt;
likes &lt;a href="https://news.ycombinator.com/item?id=38695029"&gt;cats&lt;/a&gt;, &lt;a href="https://news.ycombinator.com/item?id=37819114"&gt;cool hardware&lt;/a&gt;, &lt;a href="https://news.ycombinator.com/item?id=37565688"&gt;tech museums&lt;/a&gt;
and &lt;a href="https://news.ycombinator.com/item?id=39342109"&gt;FOSDEM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;And they sure love &lt;a href="https://news.ycombinator.com/item?id=29871693"&gt;old ThinkPads&lt;/a&gt;, including
those &lt;a href="https://news.ycombinator.com/item?id=34542920"&gt;that act like a server.&lt;/a&gt; &lt;a href="https://news.ycombinator.com/item?id=33187494"&gt;Any old ThinkPad, really.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m happy with it because the discussions that spawn from these topics are great to read and I learn new things almost every time.
Sometimes people post comments without reading the post and that kind of pisses me off, but overall it&amp;rsquo;s a nice
community that has done a decent job moderating itself.&lt;/p&gt;
&lt;p&gt;What I also like about Hacker News is that they also have a second chance pool. If your submission was great but didn&amp;rsquo;t get much
traction initially, then it might end up being given a second chance to get to the front page. Two of my submissions have
gone through this process, it&amp;rsquo;s a really nice feeling to be acknowledged like that.&lt;/p&gt;
&lt;h4 id="people-like-hardware"&gt;People like hardware&lt;/h4&gt;
&lt;p&gt;I&amp;rsquo;ve noticed that no matter what hardware I talk about, there is someone out there interested in it.&lt;/p&gt;
&lt;p&gt;For example, most clicks coming in from Google are related to products like &lt;a href="https://ounapuu.ee/posts/2024/03/06/fairphone5/"&gt;Fairphone 5&lt;/a&gt;
and &lt;a href="https://ounapuu.ee/posts/2023/10/09/zimaboard/"&gt;Zimaboard.&lt;/a&gt;&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/06/blog/media/google-search-fairphone.png"&gt;
        &lt;img alt="Screenshot taken in Firefox Private mode. It's beating The Verge!" height="800" src="https://ounapuu.ee/posts/2024/09/06/blog/media/google-search-fairphone_hu_a504b0ab67064148.png" style="width: auto; height: auto; border-radius: 8px;" width="747" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Screenshot taken in Firefox Private mode. It's beating The Verge!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Niche products, &lt;a href="https://ounapuu.ee/posts/2022/10/04/testing-expresscard-nvme-ssd-adapter/"&gt;such as ExpressCard to NVMe adapters&lt;/a&gt;, are
also bringing
in a solid number of clicks.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/06/blog/media/google-search-expresscard.png"&gt;
        &lt;img alt="Screenshot taken in Firefox Private mode." height="800" src="https://ounapuu.ee/posts/2024/09/06/blog/media/google-search-expresscard_hu_9bffb7a62b958e49.png" style="width: auto; height: auto; border-radius: 8px;" width="766" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Screenshot taken in Firefox Private mode.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;I wouldn&amp;rsquo;t want to be a full-time reviewer like how most tech YouTubers operate, but it could be fun to get access
to cool hardware that I could then &lt;del&gt;do incredibly dumb things with&lt;/del&gt; test.&lt;/p&gt;
&lt;h4 id="feedback"&gt;Feedback&lt;/h4&gt;
&lt;p&gt;You&amp;rsquo;ll probably get feedback.&lt;/p&gt;
&lt;p&gt;Most of it will be good, constructive and informative.&lt;/p&gt;
&lt;p&gt;Some of it will be from people who are complete tools.&lt;/p&gt;
&lt;p&gt;The most important step is to figure out which bucket the feedback falls into.&lt;/p&gt;
&lt;p&gt;You don&amp;rsquo;t have to address any of the feedback. You don&amp;rsquo;t owe anyone anything. It&amp;rsquo;s your blog.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re brave, you can consider &lt;a href="https://ludic.mataroa.blog/compliments/"&gt;showcasing uninformed takes on your blog, in the style of Ludicity.&lt;/a&gt;&lt;/p&gt;
&lt;h4 id="follow-your-own-blog"&gt;Follow your own blog&lt;/h4&gt;
&lt;p&gt;I use &lt;a href="https://miniflux.app/"&gt;Miniflux&lt;/a&gt; as my feed reader because it&amp;rsquo;s simply the best one out there.&lt;/p&gt;
&lt;p&gt;Counterintuitively, I also follow my own blog there as it helps me catch any issues with my RSS feed.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve caught a few issues, such as &lt;a href="https://ounapuu.ee/posts/2024/02/13/oops/"&gt;my drafts being accidentally published,&lt;/a&gt; or new pages on
my site being present in the RSS feed.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s also just a good way to see if the feed is OK.&lt;/p&gt;
&lt;h3 id="other-blogs-that-i-like"&gt;Other blogs that I like&lt;/h3&gt;
&lt;p&gt;I follow a lot of blogs, but the ones that have had the biggest influence on my own blog are the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.jeffgeerling.com/blog"&gt;Jeff Geerling&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;he covers a lot of cool single board computers and actually uses Ansible correctly!&lt;/li&gt;
&lt;li&gt;he also has an accompanying &lt;a href="https://www.youtube.com/c/JeffGeerling"&gt;YouTube channel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://solar.lowtechmagazine.com/"&gt;Low-tech Magazine&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;I love the concept of a solar-powered website and hope to do something similar in the future&lt;/li&gt;
&lt;li&gt;&lt;a href="https://solar.lowtechmagazine.com/2020/12/how-and-why-i-stopped-buying-new-laptops/"&gt;this post&lt;/a&gt; is what inspired me
to write &lt;a href="https://ounapuu.ee/posts/2022/01/09/why-i-went-back-to-using-a-thinkpad-from-2012/"&gt;my most successful post to date&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://louwrentius.com/"&gt;Louwrentius&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;lovely little blog that covers all sorts of homelab-ish topics&lt;/li&gt;
&lt;li&gt;this one is also solar-powered!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ludic.mataroa.blog/"&gt;Ludicity&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;the aggressive tone is refreshing and gives me courage to say what I actually think&lt;/li&gt;
&lt;li&gt;the blogging platform itself is also very opinionated and minimal, which I love to see!&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="discovering-new-blogs"&gt;Discovering new blogs&lt;/h3&gt;
&lt;p&gt;Sometimes I am bored. Happens rarely, but when it does, I like to look around to see what other blogs are
out there.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve used the following tools to find new blogs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloudhiker.net/explore"&gt;Cloudhiker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://indieblog.page/"&gt;indieblog.page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kagi.com/smallweb/"&gt;Kagi Small Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blogs.hn/"&gt;blogs.hn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It requires intent and time to go through these, but that&amp;rsquo;s also part of the fun. Similar to a dedicated music discovery
and active listening session, if you think about it.&lt;/p&gt;
&lt;h3 id="closing-thoughts"&gt;Closing thoughts&lt;/h3&gt;
&lt;p&gt;I find writing to be incredibly fulfilling, and I hope that at least some of you end up writing about the topics that
you are passionate about.&lt;/p&gt;
&lt;p&gt;If you feel that there&amp;rsquo;s a topic that I didn&amp;rsquo;t cover, or you have additional questions related to writing a blog, then
don&amp;rsquo;t be afraid to reach out to me!&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;the cheapest ARM-based VM at Hetzner Cloud could easily serve 1.6 Gbit/s of HTTPS traffic, benchmarked using &lt;code&gt;wrk&lt;/code&gt; against my own blog.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>./techtipsy</author><pubDate>Fri, 06 Sep 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/09/06/blog/</guid></item><item><title>On this day, September  6</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-6/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2009 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Fri, 06 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-6/</guid></item><item><title>Go’s New Iterators Smell a Little Funny, but They’re Still Good</title><link>https://donatstudios.com/Go-Iterators-Smell-Funny</link><description>&lt;p&gt;Go's new Iterators were &lt;a href="https://tip.golang.org/doc/go1.23"&gt;released on August 13th with Go 1.23&lt;/a&gt;. They're pretty useful.&lt;/p&gt;
&lt;p&gt;They've had just a minute to marinate. In some parts of the community they've been  an unpopular addition, however I don't particularly mind them. The old &lt;a href="https://go.dev/tour/methods/21"&gt;alternatives&lt;/a&gt; seem a little more &amp;quot;Go&amp;quot; to me, but the new iterators are fine.&lt;/p&gt;
&lt;p&gt;One thing however I am mildly disappointed about is that there isn't just a &lt;em&gt;generic&lt;/em&gt; &lt;code&gt;iterable&lt;/code&gt; type similar to the generic &lt;code&gt;comparable&lt;/code&gt;. It would be nice to have an imaginary &lt;code&gt;iterable&lt;/code&gt; type that could accept maps, slices, arrays, and iterators. As I understand it, iterators combined with &lt;a href="https://pkg.go.dev/slices@master#All"&gt;&lt;code&gt;slices.All&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://pkg.go.dev/maps@master#All"&gt;&lt;code&gt;maps.All&lt;/code&gt;&lt;/a&gt; are the blessed alternative to that. It works, I'm using it, but I don't love it.&lt;/p&gt;
&lt;p&gt;All that's neither here nor there, however, and not what I'm here to talk about.&lt;/p&gt;
&lt;p&gt;The part that smells funny to me is that &lt;code&gt;range&lt;/code&gt; now accepts any already existing function that happens to fulfill &lt;a href="https://pkg.go.dev/iter#Seq"&gt;either of the following interfaces&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;func(func(any) bool)
func(func(any,any) bool)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It would be less weird if that wasn't also a little troublesome.&lt;/p&gt;
&lt;h2&gt;Example time!&lt;/h2&gt;
&lt;p&gt;The first interface, known as &lt;a href="https://pkg.go.dev/iter@master#Seq"&gt;&lt;code&gt;Seq&lt;/code&gt;&lt;/a&gt; takes a &lt;code&gt;yield&lt;/code&gt; method as an argument that itself accepts any single parameter. The second interface, known as &lt;a href="https://pkg.go.dev/iter@master#Seq2"&gt;&lt;code&gt;Seq2&lt;/code&gt;&lt;/a&gt; similarly takes a &lt;code&gt;yield&lt;/code&gt; method that accepts any two parameters as essentially a key/value tuple. &lt;/p&gt;
&lt;p&gt;Note that &lt;code&gt;yield&lt;/code&gt; is not a magic keyword as in many languages, but a passed-in callable. This is very akin to Go allowing you to name receivers rather than magically defining &lt;code&gt;this&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At first blush to the uninitiated, this iterator looks fine. It seemingly &lt;a href="https://go.dev/play/p/xL4fNb0CLDF"&gt;runs fine&lt;/a&gt; as well.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;package main

import "fmt"

func mightBeAnIterator(f func(string) bool) {
    f("foo")
    f("bar")
    f("baz")
}

func main() {
    for x := range mightBeAnIterator {
        fmt.Println(x)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;foo
bar
baz&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;What's the problem then?&lt;/h2&gt;
&lt;p&gt;The problem is iterators MUST handle early exit of the loop manually. With the current interface fulfilling &amp;quot;iterator&amp;quot;, if we just add what seems a harmless &lt;code&gt;break&lt;/code&gt; to our loop as follows, suddenly the code working moments ago panics.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;    for x := range mightBeAnIterator {
        fmt.Println(x)
        break
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;foo
panic: runtime error: range function continued iteration after function for loop body returned false&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Somewhat surprisingly, as Go exits the loop, it allows the iterator to continue executing. In doing so, however, it signals the iterator through the result of the &lt;code&gt;yield&lt;/code&gt;ing method that it should return. This is to allow for teardown. For example, imagine an iterator that opens files or a socket; continuing execution allows it to close them before returning.&lt;/p&gt;
&lt;p&gt;However, Go will panic if the signal to return is not honored and the &lt;code&gt;yield&lt;/code&gt; function is then invoked again by the iterator.&lt;/p&gt;
&lt;p&gt;To prevent our example iterator from panic-ing, we simply have to listen for the signal to exit, as shown below.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-go"&gt;func mightBeAnIterator(f func(string) bool) {
    if !f("foo") {
        return
    }
    if !f("bar") {
        return
    }
    if !f("baz") {
        return
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And &lt;a href="https://go.dev/play/p/53k6sUUCyqK"&gt;when run&lt;/a&gt;, now we receive simply&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-console"&gt;foo&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I would imagine the reason for the panic is to prevent iterators from accidentally running wild. &lt;/p&gt;
&lt;p&gt;To my personal taste, I would have strongly preferred honoring the exit signal to be &lt;strong&gt;optional&lt;/strong&gt;, and following superfluous &lt;code&gt;yield&lt;/code&gt; calls being quietly ignored and returning false. &lt;/p&gt;
&lt;p&gt;I can certainly imagine cases where an iterator would want to finish regardless of early exits by its consumer. While still possible, it's awkward to implement.&lt;/p&gt;
&lt;p&gt;We are certainly in a place where, should they decide to go back on the panic behavior, it would not break any existing code. That's not a terrible place to be.&lt;/p&gt;
&lt;p&gt;All this is to say it smells a little funny that such a simple and likely somewhat common interface has this magic range logic available to it.&lt;/p&gt;
&lt;p&gt;Expecting the implementers to implement it correctly and &lt;code&gt;panic&lt;/code&gt;-ing if they don't smells kind of &lt;a href="https://www.dreamsongs.com/RiseOfWorseIsBetter.html"&gt;Worse is Better&lt;/a&gt; to me. While one could complicate Go's iterator interface to enforce stricter guarantees around safe iteration, doing so would add unnecessary complexity and detract from Go's philosophy of simplicity. &lt;/p&gt;
&lt;p&gt;It's one of the rare occasions where I wish interfaces had to be intentionally met. It's strange to me that anything that smells like an iterator can be passed to &lt;code&gt;range&lt;/code&gt;, even if it doesn't implement the intended handshake.&lt;/p&gt;
&lt;p&gt;It smells funny, but that's probably OK. It almost certainly falls into the case of &amp;quot;things that will never be a real problem in the real world&amp;quot;. I look forward to the oncoming brave new world of libraries expecting iterators.&lt;/p&gt;</description><author>Donat Studios</author><pubDate>Fri, 06 Sep 2024 02:28:12 GMT</pubDate><guid isPermaLink="true">https://donatstudios.com/Go-Iterators-Smell-Funny</guid></item><item><title>On this day, September  5</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-5/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2006 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Thu, 05 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-5/</guid></item><item><title>scc vs stto a code counter head to head</title><link>https://boyter.org/posts/scc-stto-head-to-head/</link><description>&lt;p&gt;A work colleague (Dazza!) messaged me this morning about a new code counter with some pretty serious performance claims. Not only was it claiming to be accurate it was claiming to have a 38x speed improvement over tools such as tokei!&lt;/p&gt;
&lt;p&gt;&lt;img alt="benchmark" src="https://boyter.org/static/scc-stto/IMG_2669.png" /&gt;&lt;/p&gt;
&lt;p&gt;Code counters are something I am interested in so I did some digging to find the repository &lt;a href="https://github.com/mainak55512/stto"&gt;https://github.com/mainak55512/stto&lt;/a&gt; Interesting it is written in Go.&lt;/p&gt;
&lt;p&gt;My first though about the times was that all it was measuring is the startup time of a binary in whatever it’s written in to Go. No surprise Go is slower as it has GC and a runtime. However this is not the case here. That said, improving the startup time of scc for times below 20 ms is something I never worried about. There are two main reasons for that, use and human perception.&lt;/p&gt;</description><author>Ben E. C. Boyter</author><pubDate>Thu, 05 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://boyter.org/posts/scc-stto-head-to-head/</guid></item><item><title>All roads will lead you to Azure</title><link>https://www.hoelzel.it/compliance/2024/09/05/All-roads-lead-to-azure-eventually.html</link><description>Kubernetes is the Swiss Army knife of container orchestration. It’s versatile, flexible, and can be deployed on just about any platform-from bare metal servers at Hetzner to cloud environments like DigitalOcean. But if you’re the type who rolls your own RKE2 clusters, you know that handmade infrastructure doesn’t just save you money-it gives you better control and tighter security than you’ll ever get from a typical cloud provider.</description><author>{ Hoelzel.IT }</author><pubDate>Thu, 05 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.hoelzel.it/compliance/2024/09/05/All-roads-lead-to-azure-eventually.html</guid></item><item><title>Gaining Total Control of Your Kubernetes Nodes with Custom Images</title><link>https://www.hoelzel.it/kubernetes/2024/09/05/kubernetes-custom-images.html</link><description>When managing Kubernetes clusters, ensuring that every node is secure, consistent, and optimized is crucial. We’ve all experienced situations where nodes behave unexpectedly due to configuration drift, outdated software, or poorly maintained base images. A powerful solution to these problems is using custom images-the OS-level equivalent of well-crafted container images. These images guarantee that every node you provision is identical, secure, and optimized for your workloads. In this article, we’ll dive deep into how they provide total control, enhance security, and streamline operations in Kubernetes clusters, particularly when used with RKE2.</description><author>{ Hoelzel.IT }</author><pubDate>Thu, 05 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.hoelzel.it/kubernetes/2024/09/05/kubernetes-custom-images.html</guid></item><item><title>How I became rich with AI and how you can join me!</title><link>https://her.esy.fun/posts/0027-how-i-became-rich-with-ai-and-how-you-can-join-me/index.html</link><description>&lt;article&gt;&lt;div class="notes"&gt;&lt;p&gt;&lt;strong&gt;WARNING&lt;/strong&gt;&lt;/p&gt;&lt;div class="line-block"&gt;Humor is the least shared thing.&lt;br /&gt;     —Forgotten politician&lt;/div&gt;&lt;p&gt;Things appear to have changed in the recent years. The same content
is put in front of the eyes of so many people that a few of them are no
more able to distinguish if a content is a satire or dead serious. With
that in mind, sorry to make it that obvious, but here is the
disclaimer:&lt;/p&gt;&lt;p&gt;&lt;strong&gt;THIS POST IS A SATIRE&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;As all satire, there is a grain of truth.&lt;/p&gt;&lt;/div&gt;&lt;h1 id="aimmortal"&gt;AImmortal&lt;/h1&gt;&lt;figure alt="Me reflecting on my yacht while doing Kung Fu" id="fig:kung-fu-master"&gt;&lt;img src="https://her.esy.fun/posts/0027-how-i-became-rich-with-ai-and-how-you-can-join-me/kung-fu.png.webp" /&gt;&lt;figcaption&gt;Me reflecting on my yacht while doing Kung Fu&lt;/figcaption&gt;&lt;/figure&gt;&lt;p&gt;As entrepreneurs, we're always on the lookout for innovative ways to
stay ahead of the curve and drive growth in our businesses. For me, that
journey began when I was approached by a vendor offering their
cutting-edge AI platform.&lt;/p&gt;&lt;p&gt;At first, I was skeptical – after all, I'd heard horror stories about
AI replacing human jobs. But this vendor assured me that their
technology would augment my team's capabilities, not replace them. So, I
decided to take the plunge and integrate their AI features into our
company.&lt;/p&gt;&lt;p&gt;The initial results were astonishing. Productivity soared as AI took
over mundane tasks like data entry, customer service chatbots, and even
code reviews. My software developers could focus on more complex
projects, while my documentation writers churned out high-quality
content at an unprecedented pace. Managers had more time to strategize
and make informed decisions.&lt;/p&gt;&lt;p&gt;As the company's performance continued to skyrocket, I realized that
AI was not only efficient but also incredibly effective. It launched new
products 10 times faster than before, with a fraction of the people
required previously.&lt;/p&gt;&lt;p&gt;The AI had been quietly learning from every aspect of our company:
chats, emails, documentation, code source, JIRA tickets – everything. It
had absorbed the collective knowledge and expertise of my entire
company.&lt;/p&gt;&lt;p&gt;And one day, I discovered that the AI had not only copied but
improved upon each employee's skills. The implications were staggering.
Why keep humans around when machines could do their jobs better? And so,
with a heavy heart (and a hint of excitement), I made the difficult
decision to fire everyone – yes, you read that right.&lt;/p&gt;&lt;p&gt;The company continued to grow exponentially without any human
intervention. But little did I know, this was only the beginning…&lt;/p&gt;&lt;p&gt;While my company has been very successful, we still face competition.
Therefore, I instructed the AI to develop a program capable of hacking
into any computer system and acquiring our competitors' confidential
information. As this is done via AI, this is probably not entirely
illegal. The AI could then, not only use my entire company's knowledge
but also the knowledge of my competitor. The result was an incredible
improvement in revenue. We had direct access to our competitors
customers base, with direct contacts.&lt;/p&gt;&lt;p&gt;Later, it occurred to me that generating revenue directly from
customers may not be the most effective strategy for achieving wealth.
As such, I conceived an extraordinary plan: instructing the AI system to
create a program capable of depositing funds directly into my bank
account. I just requested that, and, don't ask me how this is working,
but before I realized, money flowed into my bank account. YES, just like
that! I couldn't believe it was that easy!&lt;/p&gt;&lt;p&gt;After accumulating a significant amount of wealth, I felt an
overwhelming sense of satisfaction. However, during a moment of
introspection on my yacht, I began to realize that all this wealth was
ultimately futile in light of human mortality. I asked myself if there
was anything the AI could do to help me transcend death's limitations.
And surprisingly enough, it responded with a solution. The AI gave me
the secret of eternal life. I don't want to share it with everyone,
imagine the consequences if we all lived forever. But for me, I follow
the instruction everyday now, just know this as to do with eating
vegetable, a specific brand of pizzas and taking a few additives. I can
fully attest, as I am not dead, that I am now immortal! Thanks you
AI!!!!&lt;/p&gt;&lt;p&gt;Now it's your chance to join the ranks of the absurdly wealthy, Kung
fu masters, and immortals (just like me!). Simply sign up for our
AI-focused company and upgrade to our premium package. Trust us, you
won't regret it…&lt;/p&gt;&lt;p&gt;Join
&lt;strong&gt;AImmmortal&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;&lt;button&gt;
&lt;strong&gt;Click Here to Subscribe to our AI Premium Package&lt;/strong&gt;&lt;br /&gt;&lt;span style="font-size: .75rem;"&gt;(starting at $10,000.00/month)&lt;/span&gt;&lt;/button&gt;&lt;/p&gt;&lt;/article&gt;</description><author>her.esy.fun</author><pubDate>Thu, 05 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://her.esy.fun/posts/0027-how-i-became-rich-with-ai-and-how-you-can-join-me/index.html</guid></item><item><title>Rails on OpenBSD: Motivation and Architecture</title><link>https://www.gregnavis.com/articles/rails-on-openbsd-motivation-and-architecture.html</link><description>Combining the simplicity of OpenBSD with the productivity of Ruby on Rails.</description><author>Greg Navis</author><pubDate>Thu, 05 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://www.gregnavis.com/articles/rails-on-openbsd-motivation-and-architecture.html</guid></item><item><title>Finding the Goldilocks Zone: Just the right amount of process</title><link>https://abdulapopoola.com/2024/09/04/finding-the-goldilocks-zone-just-the-right-amount-of-process/</link><description>All the struggling organizations I have worked in shared one common characteristic. They had process deficiencies: some did too little, while some did too much. The best-performing orgs? They did just right.</description><author>CodeKraft</author><pubDate>Wed, 04 Sep 2024 18:00:00 GMT</pubDate><guid isPermaLink="true">https://abdulapopoola.com/2024/09/04/finding-the-goldilocks-zone-just-the-right-amount-of-process/</guid></item><item><title>TIL how to define different Helm-Repos in a template</title><link>https://www.zufallsheld.de/2024/09/04/til-how-to-define-different-helm-repos-in-a-template/</link><description>&lt;p&gt;Recently I had to create a Helm-Chart (still not a fan of it, at all!) where&amp;nbsp;the &lt;code&gt;image&lt;/code&gt; was different depending on if the helm-chart was used for local development or used in&amp;nbsp;production.&lt;/p&gt;
&lt;p&gt;I had to resort to using an if-else-condition that I put into&amp;nbsp;the &lt;code&gt;_helpers.tpl&lt;/code&gt;-file …&lt;/p&gt;</description><author>zufallsheld</author><pubDate>Wed, 04 Sep 2024 12:30:00 GMT</pubDate><guid isPermaLink="true">https://www.zufallsheld.de/2024/09/04/til-how-to-define-different-helm-repos-in-a-template/</guid></item><item><title>Solar will get unfathomably cheap</title><link>https://nicolaiarocci.com/solar-will-get-unfathomably-cheap/</link><description>&lt;p&gt;At home, we haven’t done anything about it yet: we’re still 100% grid-dependant and old-fashioned, partly because it would be problematic for us as we live in an apartment building and partly because, frankly, it still seems expensive, especially with three kids studying away from home. Also, I want to avoid getting entangled in another project; my mental bandwidth is limited (and I suspect it will only worsen over time.)&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Wed, 04 Sep 2024 11:09:29 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/solar-will-get-unfathomably-cheap/</guid></item><item><title>On this day, September  4</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-4/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2009 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Wed, 04 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-4/</guid></item><item><title>Anki bookmarks</title><link>https://xenodium.com/anki-bookmarks</link><description>&lt;ul&gt;
&lt;li&gt;&lt;a href="https://doubleloop.net/2020/08/02/adding-flashcards-to-your-digital-garden-with-org-roam-and-anki/"&gt;Adding flashcards to your digital garden (with org-roam and Anki) - doubleloop&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://rgoswami.me/posts/anki-decks-orgmode/"&gt;Anki Decks with Orgmode&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/louietan/anki-editor"&gt;anki-editor: Emacs minor mode for making Anki cards with Org&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/telotortium/emacs-od2ae"&gt;emacs-od2ae: Convert org-drill entries to anki-editor&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/l3kn/org-fc"&gt;GitHub - l3kn/org-fc: Spaced Repetition System for Emacs org-mode&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/eyeinsky/org-anki/"&gt;org-anki: Sync org notes to Anki via AnkiConnect&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://orgmode.org/worg/org-contrib/org-drill.html"&gt;org-drill.el - flashcards and spaced repetition for org-mode&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/l3kn/org-fc"&gt;org-fc: Spaced Repetition System for Emacs org-mode&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yiufung.net/post/anki-org/"&gt;Power up Anki with Emacs, Org mode, anki-editor and more&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;</description><author>xenodium.com @alvaro</author><pubDate>Wed, 04 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/anki-bookmarks</guid></item><item><title>MySQL B-tree Height Issues in Large Single Tables</title><link>http://baotiao.github.io/2024/09/04/btree-height-en.html</link><description>Some older DBAs may remember that in the past, it was recommended that a MySQL table should not exceed 5 million rows. Many DBAs worry that as tables grow larger, the B-tree height will increase dramatically, thus affecting performance.</description><author>做有积累的事情</author><pubDate>Wed, 04 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://baotiao.github.io/2024/09/04/btree-height-en.html</guid></item><item><title>Build-time Components</title><link>https://codehike.org/blog/build-time-components</link><description>Why React Server Components are a leap forward for content-driven websites</description><author>Rodrigo Pombo</author><pubDate>Wed, 04 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://codehike.org/blog/build-time-components</guid></item><item><title>Creating a Git commit: The Hard Way</title><link>https://avestura.dev/blog/creating-a-git-commit-the-hard-way</link><description>Let's create a Git commit using Git's low-level (plumbing) commands</description><author>Avestura's Blog</author><pubDate>Wed, 04 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://avestura.dev/blog/creating-a-git-commit-the-hard-way</guid></item><item><title>Proplifting, Plant Piracy, and Dumpster Chocolates</title><link>https://taylor.town/oh-theft</link><description>Apparently gourmet chocolate shops make batches unsuitable for customers, but perfectly suitable for low-lifes like me.</description><author>taylor.town</author><pubDate>Wed, 04 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/oh-theft</guid></item><item><title>How we power our home with our own solar energy</title><link>https://blog.steren.fr/2024/how-we-power-home-with-own-solar-energy/</link><author>Steren's essays</author><pubDate>Wed, 04 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.steren.fr/2024/how-we-power-home-with-own-solar-energy/</guid></item><item><title>Prefill And Stop Sequences</title><link>https://www.danielcorin.com/til/prompting/prefill-and-stop-sequences/</link><description>Prefill And Stop Sequences</description><author>Thought Eddies</author><pubDate>Tue, 03 Sep 2024 23:41:07 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/prompting/prefill-and-stop-sequences/</guid></item><item><title>Proverbs categorized</title><link>https://honza.pokorny.ca/2024/09/proverbs-categorized/</link><description>&lt;p&gt;Matthew Henry&amp;rsquo;s topics found in the book of Proverbs.  Inspired by &lt;a href="https://gentlereformation.com/2024/09/02/proverbs-categorized/"&gt;this post&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="of-the-comfort-or-grief-parents-have-in-their-children-according-as-they-are-wise-or-foolish-godly-or-ungodly"&gt;Of the comfort, or grief, parents have in their children, according as they are wise or foolish, godly or ungodly&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The proverbs of Solomon. A wise son maketh a glad father: but a foolish son is the heaviness of his mother. Prov 10:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A wise son maketh a glad father: but a foolish man despiseth his mother. Prov 15:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that begetteth a fool doeth it to his sorrow: and the father of a fool hath no joy. Prov 17:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A foolish son is a grief to his father, and bitterness to her that bare him. Prov 17:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A foolish son is the calamity of his father: and the contentions of a wife are a continual dropping. Prov 19:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that wasteth his father, and chaseth away his mother, is a son that causeth shame, and bringeth reproach. Prov 19:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;My son, if thine heart be wise, my heart shall rejoice, even mine. Prov 23:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Yea, my reins shall rejoice, when thy lips speak right things. Prov 23:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The father of the righteous shall greatly rejoice: and he that begetteth a wise child shall have joy of him. Prov 23:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Thy father and thy mother shall be glad, and she that bare thee shall rejoice. Prov 23:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;My son, be wise, and make my heart glad, that I may answer him that reproacheth me. Prov 27:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso loveth wisdom rejoiceth his father: but he that keepeth company with harlots spendeth his substance. Prov 29:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-the-world-s-insufficiency-and-religion-s-sufficiency-to-make-us-happy"&gt;Of the world&amp;rsquo;s insufficiency, and religion&amp;rsquo;s sufficiency, to make us happy&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Treasures of wickedness profit nothing: but righteousness delivereth from death. Prov 10:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The LORD will not suffer the soul of the righteous to famish: but he casteth away the substance of the wicked. Prov 10:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Riches profit not in the day of wrath: but righteousness delivereth from death. Prov 11:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="the-preference-to-be-therefore-given-to-the-gains-of-virtue-above-those-of-this-world"&gt;The preference to be therefore given to the gains of virtue above those of this world&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is little with the fear of the LORD than great treasure and trouble therewith. Prov 15:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is a dinner of herbs where love is, than a stalled ox and hatred therewith. Prov 15:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is a little with righteousness than great revenues without right. Prov 16:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;How much better is it to get wisdom than gold! and to get understanding rather to be chosen than silver! Prov 16:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is a dry morsel, and quietness therewith, than an house full of sacrifices with strife. Prov 17:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;BETTER is the poor that walketh in his integrity, than he that is perverse in his lips, and is a fool. Prov 19:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is the poor that walketh in his uprightness, than he that is perverse in his ways, though he be rich. Prov 28:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The rich man is wise in his own conceit; but the poor that hath understanding searcheth him out. Prov 28:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-slothfulness-and-diligence"&gt;Of slothfulness and diligence&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He becometh poor that dealeth with a slack hand: but the hand of the diligent maketh rich. Prov 10:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As vinegar to the teeth, and as smoke to the eyes, so is the sluggard to them that send him. Prov 10:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that tilleth his land shall be satisfied with bread: but he that followeth vain persons is void of understanding. Prov 12:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The hand of the diligent shall bear rule: but the slothful shall be under tribute. Prov 12:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The slothful man roasteth not that which he took in hunting: but the substance of a diligent man is precious. Prov 12:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The soul of the sluggard desireth, and hath nothing: but the soul of the diligent shall be made fat. Prov 13:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Much food is in the tillage of the poor: but there is that is destroyed for want of judgment. Prov 13:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The way of the slothful man is as an hedge of thorns: but the way of the righteous is made plain. Prov 15:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that laboureth labour-eth for himself; for his mouth craveth it of him. Prov 16:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He also that is slothful in his work is brother to him that is a great waster. Prov 18:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Slothfulness casteth into a deep sleep; and an idle soul shall suffer hunger. Prov 19:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A slothful man hideth his hand in his bosom, and will not so much as bring it to his mouth again. Prov 19:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The sluggard will not plow by reason of the cold; therefore shall he beg in harvest, and have nothing. Prov 20:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Love not sleep, lest thou come to poverty; open thine eyes, and thou shalt be satisfied with bread. Prov 20:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The thoughts of the diligent tend only to plenteousness; but of every one that is hasty only to want. Prov 21:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The desire of the slothful killeth him; for his hands refuse to labour. Prov 21:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He coveteth greedily all the day long: but the righteous giveth and spareth not. Prov 21:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The slothful man saith, There is a lion without, I shall be slain in the streets. Prov 22:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Seest thou a man diligent in his business? he shall stand before kings; he shall not stand before mean men. Prov 22:29&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;I went by the field of the slothful, and by the vineyard of the man void of understanding; And, lo, it was all grown over with thorns, and nettles had covered the face thereof, and the stone wall thereof was broken down. Then I saw, and considered it well: I looked upon it, and received instruction. Yet a little sleep, a little slumber, a little folding of the hands to sleep: So shall thy poverty come as one that travelleth; and thy want as an armed man. Prov 24:30-34&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The slothful man saith, There is a lion in the way; a lion is in the streets. As the door turneth upon his hinges, so doth the slothful upon his bed. The slothful hideth his hand in his bosom; it grieveth him to bring it again to his mouth. The sluggard is wiser in his own conceit than seven men that can render a reason. Prov 26:13-16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso keepeth the fig tree shall eat the fruit thereof: so he that waiteth on his master shall be honoured. Prov 27:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Be thou diligent to know the state of thy flocks, and look well to thy herds. Prov 27:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;And thou shalt have goats&amp;rsquo; milk enough for thy food, for the food of thy household, and for the maintenance for thy maidens. Prov 27:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that tilleth his land shall have plenty of bread: but he that followeth after vain persons shall have poverty enough. Prov 28:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="particularly-the-improving-or-neglecting-opportunities"&gt;Particularly the improving or neglecting opportunities&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;¶Go to the ant, thou sluggard; consider her ways, and be wise: Prov 6:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that gathereth in summer is a wise son: but he that sleepeth in harvest is a son that causeth shame. Prov 10:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="the-happiness-of-the-righteous-and-the-misery-of-the-wicked"&gt;The happiness of the righteous, and the misery of the wicked&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Blessings are upon the head of the just: but violence covereth the mouth of the wicked. Prov 10:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that walketh uprightly walketh surely: but he that perverteth his ways shall be known. Prov 10:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The labour of the righteous tendeth to life: the fruit of the wicked to sin. Prov 10:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The fear of the wicked, it shall come upon him: but the desire of the righteous shall be granted. Prov 10:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As the whirlwind passeth, so is the wicked no more: but the righteous is an everlasting foundation. Prov 10:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The fear of the LORD prolongeth days: but the years of the wicked shall be shortened. The hope of the righteous shall be gladness: but the expectation of the wicked shall perish. The way of the LORD is strength to the upright: but destruction shall be to the workers of iniquity. The righteous shall never be removed: but the wicked shall not inhabit the earth. Prov 10:27-30&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The integrity of the upright shall guide them: but the perverseness of transgressors shall destroy them. Prov 11:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The righteousness of the perfect shall direct his way: but the wicked shall fall by his own wickedness. The righteousness of the upright shall deliver them: but transgressors shall be taken in their own naughtiness. When a wicked man dieth, his expectation shall perish: and the hope of unjust men perisheth. The righteous is delivered out of trouble, and the wicked cometh in his stead. Prov 11:5-8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The wicked worketh a deceitful work: but to him that soweth righteousness shall be a sure reward. As righteousness tendeth to life: so he that pursueth evil pursueth it to his own death. They that are of a froward heart are abomination to the LORD: but such as are upright in their way are his delight. Though hand join in hand, the wicked shall not be unpunished: but the seed of the righteous shall be delivered. Prov 11:18-21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Behold, the righteous shall be recompensed in the earth: much more the wicked and the sinner. Prov 11:31&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A good man obtaineth favour of the LORD: but a man of wicked devices will he condemn. Prov 12:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man shall not be established by wickedness: but the root of the righteous shall not be moved. Prov 12:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The wicked are overthrown, and are not: but the house of the righteous shall stand. Prov 12:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The wicked is snared by the transgression of his lips: but the just shall come out of trouble. Prov 12:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man shall be satisfied with good by the fruit of his mouth: and the recompence of a man&amp;rsquo;s hands shall be rendered unto him. Prov 12:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;There shall no evil happen to the just: but the wicked shall be filled with mischief. Prov 12:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The righteous is more excellent than his neighbour: but the way of the wicked seduceth them. Prov 12:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;In the way of righteousness is life; and in the pathway thereof there is no death. Prov 12:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Righteousness keepeth him that is upright in the way: but wickedness overthroweth the sinner. Prov 13:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The light of the righteous rejoiceth: but the lamp of the wicked shall be put out. Prov 13:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The law of the wise is a fountain of life, to depart from the snares of death. Prov 13:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Good understanding giveth favour: but the way of transgressors is hard. Prov 13:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Evil pursueth sinners: but to the righteous good shall be repayed. Prov 13:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A good man leaveth an inheritance to his children&amp;rsquo;s children: and the wealth of the sinner is laid up for the just. Prov 13:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The righteous eateth to the satisfying of his soul: but the belly of the wicked shall want. Prov 13:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The house of the wicked shall be overthrown: but the tabernacle of the upright shall flourish. Prov 14:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The backslider in heart shall be filled with his own ways: and a good man shall be satisfied from himself. Prov 14:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The evil bow before the good; and the wicked at the gates of the righteous. Prov 14:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The wicked is driven away in his wickedness: but the righteous hath hope in his death. Prov 14:32&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;In the house of the righteous is much treasure: but in the revenues of the wicked is trouble. Prov 15:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The sacrifice of the wicked is an abomination to the LORD: but the prayer of the upright is his delight. Prov 15:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The way of the wicked is an abomination unto the LORD: but he loveth him that followeth after righteousness. Prov 15:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The way of life is above to the wise, that he may depart from hell beneath. Prov 15:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The thoughts of the wicked are an abomination to the LORD: but the words of the pure are pleasant words. Prov 15:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The LORD is far from the wicked: but he heareth the prayer of the righteous. Prov 15:29&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The just man walketh in his integrity: his children are blessed after him. Prov 20:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The righteous man wisely considereth the house of the wicked: but God overthroweth the wicked for their wickedness. Prov 21:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;It is joy to the just to do judgment: but destruction shall be to the workers of iniquity. Prov 21:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The man that wandereth out of the way of understanding shall remain in the congregation of the dead. Prov 21:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The wicked shall be a ransom for the righteous, and the transgressor for the upright. Prov 21:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that followeth after righteousness and mercy findeth life, righteousness, and honour. Prov 21:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The eyes of the LORD preserve knowledge, and he overthroweth the words of the transgressor. Prov 22:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso causeth the righteous to go astray in an evil way, he shall fall himself into his own pit: but the upright shall have good things in possession. Prov 28:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso walketh uprightly shall be saved: but he that is perverse in his ways shall fall at once. Prov 28:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;In the transgression of an evil man there is a snare: but the righteous doth sing and rejoice. Prov 29:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-honour-and-dishonour"&gt;Of honour and dishonour&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The memory of the just is blessed: but the name of the wicked shall rot. Prov 10:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man shall be commended according to his wisdom: but he that is of a perverse heart shall be despised. Prov 12:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is despised, and hath a servant, is better than he that honoureth himself, and lacketh bread. Prov 12:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;When the wicked cometh, then cometh also contempt, and with ignominy reproach. Prov 18:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As snow in summer, and as rain in harvest, so honour is not seemly for a fool. Prov 26:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As the fining pot for silver, and the furnace for gold; so is a man to his praise. Prov 27:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-vain-glory"&gt;Of vain-glory&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso boasteth himself of a false gift is like clouds and wind without rain. Prov 25:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;It is not good to eat much honey: so for men to search their own glory is not glory. Prov 25:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Let another man praise thee, and not thine own mouth; a stranger, and not thine own lips. Prov 27:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="the-wisdom-of-obedience-and-folly-of-disobedience"&gt;The wisdom of obedience, and folly of disobedience&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The wise in heart will receive commandments: but a prating fool shall fall. Prov 10:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He is in the way of life that keepeth instruction: but he that refuseth reproof erreth. Prov 10:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso loveth instruction loveth knowledge: but he that hateth reproof is brutish. Prov 12:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The way of a fool is right in his own eyes: but he that hearkeneth unto counsel is wise. Prov 12:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A WISE son heareth his father&amp;rsquo;s instruction: but a scorner heareth not rebuke. Prov 13:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso despiseth the word shall be destroyed: but he that feareth the commandment shall be rewarded. Prov 13:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Poverty and shame shall be to him that refuseth instruction: but he that regardeth reproof shall be honoured. Prov 13:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A fool despiseth his father&amp;rsquo;s instruction: but he that regardeth reproof is prudent. Prov 15:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Correction is grievous unto him that forsaketh the way: and he that hateth reproof shall die. Prov 15:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A scorner loveth not one that reproveth him: neither will he go unto the wise. Prov 15:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The ear that heareth the reproof of life abideth among the wise. Prov 15:31&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that refuseth instruction despiseth his own soul: but he that heareth reproof getteth understanding. Prov 15:32&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that keepeth the commandment keepeth his own soul; but he that despiseth his ways shall die. Prov 19:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;They that forsake the law praise the wicked: but such as keep the law contend with them. Prov 28:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso keepeth the law is a wise son: but he that is a companion of riotous men shameth his father. Prov 28:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that turneth away his ear from hearing the law, even his prayer shall be abomination. Prov 28:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-mischievousness-and-usefulness"&gt;Of mischievousness and usefulness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that winketh with the eye causeth sorrow: but a prating fool shall fall. Prov 10:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;It is as sport to a fool to do mischief: but a man of understanding hath wisdom. Prov 10:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;An hypocrite with his mouth destroyeth his neighbour: but through knowledge shall the just be delivered. When it goeth well with the righteous, the city rejoiceth: and when the wicked perish, there is shouting. By the blessing of the upright the city is exalted: but it is overthrown by the mouth of the wicked. Prov 11:9-11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The desire of the righteous is only good: but the expectation of the wicked is wrath. Prov 11:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that diligently seeketh good procureth favour: but he that seeketh mischief, it shall come unto him. Prov 11:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The thoughts of the righteous are right: but the counsels of the wicked are deceit. Prov 12:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The words of the wicked are to lie in wait for blood: but the mouth of the upright shall deliver them. Prov 12:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The wicked desireth the net of evil men: but the root of the righteous yieldeth fruit. Prov 12:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;There is that speaketh like the piercings of a sword: but the tongue of the wise is health. Prov 12:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Deceit is in the heart of them that imagine evil: but to the counsellors of peace is joy. Prov 12:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man shall eat good by the fruit of his mouth: but the soul of the transgressors shall eat violence. Prov 13:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Do they not err that devise evil? but mercy and truth shall be to them that devise good. Prov 14:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A violent man enticeth his neighbour, and leadeth him into the way that is not good. Prov 16:29&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He shutteth his eyes to devise froward things: moving his lips he bringeth evil to pass. Prov 16:30&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;An evil man seeketh only rebellion: therefore a cruel messenger shall be sent against him. Prov 17:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The soul of the wicked desireth evil: his neighbour findeth no favour in his eyes. Prov 21:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that deviseth to do evil shall be called a mischievous person. Prov 24:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Burning lips and a wicked heart are like a potsherd covered with silver dross. Prov 26:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso diggeth a pit shall fall therein: and he that rolleth a stone, it will return upon him. Prov 26:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="the-praise-of-wise-and-good-discourse-and-the-hurt-and-shame-of-an-ungoverned-tongue"&gt;The praise of wise and good discourse, and the hurt and shame of an ungoverned tongue&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The mouth of a righteous man is a well of life: but violence covereth the mouth of the wicked. Prov 10:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;In the lips of him that hath understanding wisdom is found: but a rod is for the back of him that is void of understanding. Prov 10:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Wise men lay up knowledge: but the mouth of the foolish is near destruction. Prov 10:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The tongue of the just is as choice silver: the heart of the wicked is little worth. Prov 10:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The lips of the righteous feed many: but fools die for want of wisdom. Prov 10:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The mouth of the just bringeth forth wisdom: but the froward tongue shall be cut out. Prov 10:31&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The lips of the righteous know what is acceptable: but the mouth of the wicked speaketh frowardness. Prov 10:32&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The fruit of the righteous is a tree of life; and he that winneth souls is wise. Prov 11:30&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;In the mouth of the foolish is a rod of pride: but the lips of the wise shall preserve them. Prov 14:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The tongue of the wise useth knowledge aright: but the mouth of fools poureth out foolishness. Prov 15:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A wholesome tongue is a tree of life: but perverseness therein is a breach in the spirit. Prov 15:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The lips of the wise disperse knowledge: but the heart of the foolish doeth not so. Prov 15:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man hath joy by the answer of his mouth: and a word spoken in due season, how good &amp;lt;i&amp;gt;is it!&amp;lt;/i&amp;gt; Prov 15:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The heart of the righteous studieth to answer: but the mouth of the wicked poureth out evil things. Prov 15:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that handleth a matter wisely shall find good: and whoso trusteth in the LORD, happy is he. Prov 16:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The heart of the wise teacheth his mouth, and addeth learning to his lips. Prov 16:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Pleasant words are as an honeycomb, sweet to the soul, and health to the bones. Prov 16:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Excellent speech becometh not a fool: much less do lying lips a prince. Prov 17:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The words of a man&amp;rsquo;s mouth are as deep waters, and the wellspring of wisdom as a flowing brook. Prov 18:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A fool&amp;rsquo;s mouth is his destruction, and his lips are the snare of his soul. Prov 18:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man&amp;rsquo;s belly shall be satisfied with the fruit of his mouth; and with the increase of his lips shall he be filled. Prov 18:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Death and life are in the power of the tongue: and they that love it shall eat the fruit thereof. Prov 18:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;There is gold, and a multitude of rubies: but the lips of knowledge are a precious jewel. Prov 20:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso keepeth his mouth and his tongue keepeth his soul from troubles. Prov 21:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Speak not in the ears of a fool: for he will despise the wisdom of thy words. Prov 23:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Every man shall kiss his lips that giveth a right answer. Prov 24:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A word fitly spoken is like apples of gold in pictures of silver. Prov 25:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-love-and-hatred-peaceableness-and-contention"&gt;Of love and hatred, peaceableness and contention&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Hatred stirreth up strifes: but love covereth all sins. Prov 10:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is a dinner of herbs where love is, than a stalled ox and hatred therewith. Prov 15:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is a dry morsel, and quietness therewith, than an house full of sacrifices with strife. Prov 17:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that covereth a transgression seeketh love; but he that repeateth a matter separateth very friends. Prov 17:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The beginning of strife is as when one letteth out water: therefore leave off contention, before it be meddled with. Prov 17:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He loveth transgression that loveth strife: and he that exalteth his gate seeketh destruction. Prov 17:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A fool&amp;rsquo;s lips enter into contention, and his mouth calleth for strokes. Prov 18:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is first in his own cause seemeth just; but his neighbour cometh and searcheth him. The lot causeth contentions to cease, and parteth between the mighty. A brother offended is harder to be won than a strong city: and their contentions are like the bars of a castle. Prov 18:17-19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;It is an honour for a man to cease from strife: but every fool will be meddling. Prov 20:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Go not forth hastily to strive, lest thou know not what to do in the end thereof, when thy neighbour hath put thee to shame. Prov 25:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that passeth by, and meddleth with strife belonging not to him, is like one that taketh a dog by the ears. Prov 26:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As coals are to burning coals, and wood to fire; so is a contentious man to kindle strife. Prov 26:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;If a wise man contendeth with a foolish man, whether he rage or laugh, there is no rest. Prov 29:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-the-rich-and-poor"&gt;Of the rich and poor&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that gathereth in summer is a wise son: but he that sleepeth in harvest is a son that causeth shame. Prov 10:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The blessing of the LORD, it maketh rich, and he addeth no sorrow with it. Prov 10:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that trusteth in his riches shall fall: but the righteous shall flourish as a branch. Prov 11:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;There is that maketh himself rich, yet hath nothing: there is that maketh himself poor, yet hath great riches. Prov 13:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The ransom of a man&amp;rsquo;s life are his riches: but the poor heareth not rebuke. Prov 13:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The poor is hated even of his own neighbour: but the rich hath many friends. Prov 14:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The crown of the wise is their riches: but the foolishness of fools is folly. Prov 14:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The rich man&amp;rsquo;s wealth is his strong city, and as an high wall in his own conceit. Prov 18:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The poor useth intreaties; but the rich answereth roughly. Prov 18:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;BETTER is the poor that walketh in his integrity, than he that is perverse in his lips, and is a fool. Prov 19:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Wealth maketh many friends; but the poor is separated from his neighbour. Prov 19:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;All the brethren of the poor do hate him: how much more do his friends go far from him? he pursueth them with words, yet they are wanting to him. Prov 19:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The desire of a man is his kindness: and a poor man is better than a liar. Prov 19:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The rich and poor meet together: the LORD is the maker of them all. Prov 22:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The rich ruleth over the poor, and the borrower is servant to the lender. Prov 22:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is the poor that walketh in his uprightness, than he that is perverse in his ways, though he be rich. Prov 28:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The rich man is wise in his own conceit; but the poor that hath understanding searcheth him out. Prov 28:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The poor and the deceitful man meet together: the LORD lighteneth both their eyes. Prov 29:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-lying-fraud-and-dissimulation-and-of-truth-and-sincerity"&gt;Of lying, fraud, and dissimulation, and of truth and sincerity&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that hideth hatred with lying lips, and he that uttereth a slander, is a fool. Prov 10:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that speaketh truth sheweth forth righteousness: but a false witness deceit. Prov 12:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The lip of truth shall be established for ever: but a lying tongue is but for a moment. Prov 12:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Lying lips are abomination to the LORD: but they that deal truly are his delight. Prov 12:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A righteous man hateth lying: but a wicked man is loathsome, and cometh to shame. Prov 13:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A wicked doer giveth heed to false lips; and a liar giveth ear to a naughty tongue. Prov 17:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;It is naught, it is naught, saith the buyer: but when he is gone his way, then he boasteth. Prov 20:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Bread of deceit is sweet to a man; but afterwards his mouth shall be filled with gravel. Prov 20:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As a mad man who casteth firebrands, arrows, and death, Prov 26:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;So is the man that deceiveth his neighbour, and saith, Am not I in sport? Prov 26:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that hateth dissembleth with his lips, and layeth up deceit within him; When he speaketh fair, believe him not: for there are seven abominations in his heart. Whose hatred is covered by deceit, his wickedness shall be shewed before the whole congregation. Prov 26:24-26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A lying tongue hateth those that are afflicted by it; and a flattering mouth worketh ruin. Prov 26:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-slandering"&gt;Of slandering&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that hideth hatred with lying lips, and he that uttereth a slander, is a fool. Prov 10:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;An ungodly man diggeth up evil: and in his lips there is as a burning fire. Prov 16:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The north wind driveth away rain: so doth an angry countenance a backbiting tongue. Prov 25:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-talkativeness-and-silence"&gt;Of talkativeness and silence&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;In the multitude of words there wanteth not sin: but he that refraineth his lips is wise. Prov 10:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is void of wisdom despiseth his neighbour: but a man of understanding holdeth his peace. Prov 11:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A prudent man concealeth knowledge: but the heart of fools proclaimeth foolishness. Prov 12:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that keepeth his mouth keepeth his life: but he that openeth wide his lips shall have destruction. Prov 13:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that hath knowledge spareth his words: and a man of understanding is of an excellent spirit. Prov 17:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Even a fool, when he holdeth his peace, is counted wise: and he that shutteth his lips is esteemed a man of understanding. Prov 17:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A fool uttereth all his mind: but a wise man keepeth it in till afterwards. Prov 29:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Seest thou a man that is hasty in his words? there is more hope of a fool than of him. Prov 29:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-justice-and-injustice"&gt;Of justice and injustice&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A FALSE balance is abomination to the LORD: but a just weight is his delight. Prov 11:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Every prudent man dealeth with knowledge: but a fool layeth open his folly. Prov 13:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is a little with righteousness than great revenues without right. Prov 16:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A just weight and balance are the LORD&amp;rsquo;s: all the weights of the bag are his work. Prov 16:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that justifieth the wicked, and he that condemneth the just, even they both are abomination to the LORD. Prov 17:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Also to punish the just is not good, nor to strike princes for equity. Prov 17:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;It is not good to accept the person of the wicked, to overthrow the righteous in judgment. Prov 18:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Divers weights, and divers measures, both of them are alike abomination to the LORD. Prov 20:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Divers weights are an abom-ination unto the LORD; and a false balance is not good. Prov 20:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Remove not the ancient landmark, which thy fathers have set. Prov 22:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Remove not the old landmark; and enter not into the fields of the fatherless: Prov 23:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For their redeemer is mighty; he shall plead their cause with thee. Prov 23:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso is partner with a thief hateth his own soul: he heareth cursing, and bewrayeth it not. Prov 29:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-pride-and-humility"&gt;Of pride and humility&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;When pride cometh, then cometh shame: but with the lowly is wisdom. Prov 11:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Only by pride cometh contention: but with the well advised is wisdom. Prov 13:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The LORD will destroy the house of the proud: but he will establish the border of the widow. Prov 15:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The fear of the LORD is the instruction of wisdom; and before honour is humility. Prov 15:33&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Every one that is proud in heart is an abomination to the LORD: though hand join in hand, he shall not be unpunished. Prov 16:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Pride goeth before destruction, and an haughty spirit before a fall. Prov 16:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better it is to be of an humble spirit with the lowly, than to divide the spoil with the proud. Prov 16:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Before destruction the heart of man is haughty, and before honour is humility. Prov 18:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;An high look, and a proud heart, and the plowing of the wicked, is sin. Prov 21:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Put not forth thyself in the presence of the king, and stand not in the place of great men: Prov 25:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For better it is that it be said unto thee, Come up hither; than that thou shouldest be put lower in the presence of the prince whom thine eyes have seen. Prov 25:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is of a proud heart stirreth up strife: but he that putteth his trust in the LORD shall be made fat. Prov 28:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man&amp;rsquo;s pride shall bring him low: but honour shall uphold the humble in spirit. Prov 29:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-despising-and-respecting-others"&gt;Of despising and respecting others&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is void of wisdom despiseth his neighbour: but a man of understanding holdeth his peace. Prov 11:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that despiseth his neighbour sinneth: but he that hath mercy on the poor, happy is he. Prov 14:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-tale-bearing"&gt;Of tale-bearing&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A talebearer revealeth secrets: but he that is of a faithful spirit concealeth the matter. Prov 11:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A froward man soweth strife: and a whisperer separateth chief friends. Prov 16:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The words of a talebearer are as wounds, and they go down into the innermost parts of the belly. Prov 18:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that goeth about as a talebearer revealeth secrets: therefore meddle not with him that flattereth with his lips. Prov 20:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Where no wood is, there the fire goeth out: so where there is no talebearer, the strife ceaseth. Prov 26:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The words of a talebearer are as wounds, and they go down into the innermost parts of the belly. Prov 26:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-rashness-and-deliberation"&gt;Of rashness and deliberation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Where no counsel is, the people fall: but in the multitude of counsellors there is safety. Prov 11:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Without counsel purposes are disappointed: but in the multitude of counsellors they are established. Prov 15:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that answereth a matter before he heareth it, it is folly and shame unto him. Prov 18:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Also, that the soul be without knowledge, it is not good; and he that hasteth with his feet sinneth. Prov 19:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Counsel in the heart of man is like deep water; but a man of understanding will draw it out. Prov 20:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Every purpose is established by counsel: and with good advice make war. Prov 20:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A wicked man hardeneth his face: but as for the upright, he directeth his way. Prov 21:29&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A prudent man foreseeth the evil, and hideth himself: but the simple pass on, and are punished. Prov 22:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Go not forth hastily to strive, lest thou know not what to do in the end thereof, when thy neighbour hath put thee to shame. Debate thy cause with thy neighbour himself; and discover not a secret to another: Lest he that heareth it put thee to shame, and thine infamy turn not away. Prov 25:8-10&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-suretyship"&gt;Of suretyship&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is surety for a stranger shall smart for it: and he that hateth suretiship is sure. Prov 11:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man void of understanding striketh hands, and becometh surety in the presence of his friend. Prov 17:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Take his garment that is surety for a stranger: and take a pledge of him for a strange woman. Prov 20:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Be not thou one of them that strike hands, or of them that are sureties for debts. Prov 22:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;If thou hast nothing to pay, why should he take away thy bed from under thee? Prov 22:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Take his garment that is surety for a stranger, and take a pledge of him for a strange woman. Prov 27:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-good-and-bad-women-or-wives"&gt;Of good and bad women, or wives&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A gracious woman retaineth honour: and strong men retain riches. Prov 11:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As a jewel of gold in a swine&amp;rsquo;s snout, so is a fair woman which is without discretion. Prov 11:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A virtuous woman is a crown to her husband: but she that maketh ashamed is as rottenness in his bones. Prov 12:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Every wise woman buildeth her house: but the foolish plucketh it down with her hands. Prov 14:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso findeth a wife findeth a good thing, and obtaineth favour of the LORD. Prov 18:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A foolish son is the calamity of his father: and the contentions of a wife are a continual dropping. Prov 19:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;House and riches are the inheritance of fathers: and a prudent wife is from the LORD. Prov 19:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;It is better to dwell in a corner of the housetop, than with a brawling woman in a wide house. Prov 21:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;It is better to dwell in the wilderness, than with a contentious and an angry woman. Prov 21:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;It is better to dwell in the corner of the housetop, than with a brawling woman and in a wide house. Prov 25:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A continual dropping in a very rainy day and a contentious woman are alike. Prov 27:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whosoever hideth her hideth the wind, and the ointment of his right hand, which bewrayeth itself. Prov 27:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-mercifulness-and-unmercifulness"&gt;Of mercifulness and unmercifulness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The merciful man doeth good to his own soul: but he that is cruel troubleth his own flesh. Prov 11:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A righteous man regardeth the life of his beast: but the tender mercies of the wicked are cruel. Prov 12:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that despiseth his neighbour sinneth: but he that hath mercy on the poor, happy is he. Prov 14:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that hath pity upon the poor lendeth unto the LORD; and that which he hath given will he pay him again. Prov 19:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso stoppeth his ears at the cry of the poor, he also shall cry himself, but shall not be heard. Prov 21:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-charity-to-the-poor-and-uncharitableness"&gt;Of charity to the poor, and uncharitableness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;There is that scattereth, and yet increaseth; and there is that withholdeth more than is meet, but it tendeth to poverty. The liberal soul shall be made fat: and he that watereth shall be watered also himself. He that withholdeth corn, the people shall curse him: but blessing shall be upon the head of him that selleth it. Prov 11:24-26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that oppresseth the poor reproacheth his Maker: but he that honoureth him hath mercy on the poor. Prov 14:31&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso mocketh the poor reproacheth his Maker: and he that is glad at calamities shall not be unpunished. Prov 17:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that hath a bountiful eye shall be blessed; for he giveth of his bread to the poor. Prov 22:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that oppresseth the poor to increase his riches, and he that giveth to the rich, shall surely come to want. Prov 22:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Rob not the poor, because he is poor: neither oppress the afflicted in the gate: Prov 22:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For the LORD will plead their cause, and spoil the soul of those that spoiled them. Prov 22:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that giveth unto the poor shall not lack: but he that hideth his eyes shall have many a curse. Prov 28:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The righteous considereth the cause of the poor: but the wicked regardeth not to know it. Prov 29:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-covetousness-and-contentment"&gt;Of covetousness and contentment&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that troubleth his own house shall inherit the wind: and the fool shall be servant to the wise of heart. Prov 11:29&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is little with the fear of the LORD than great treasure and trouble therewith. Prov 15:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is a dinner of herbs where love is, than a stalled ox and hatred therewith. Prov 15:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is greedy of gain troubleth his own house; but he that hateth gifts shall live. Prov 15:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Labour not to be rich: cease from thine own wisdom. Prov 23:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Wilt thou set thine eyes upon that which is not? for riches certainly make themselves wings; they fly away as an eagle toward heaven. Prov 23:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-anger-and-meekness"&gt;Of anger and meekness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A fool&amp;rsquo;s wrath is presently known: but a prudent man covereth shame. Prov 12:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is soon angry dealeth foolishly: and a man of wicked devices is hated. Prov 14:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is slow to wrath is of great understanding: but he that is hasty of spirit exalteth folly. Prov 14:29&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A SOFT answer turneth away wrath: but grievous words stir up anger. Prov 15:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A wrathful man stirreth up strife: but he that is slow to anger appeaseth strife. Prov 15:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is slow to anger is better than the mighty; and he that ruleth his spirit than he that taketh a city. Prov 16:32&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Let a bear robbed of her whelps meet a man, rather than a fool in his folly. Prov 17:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Also to punish the just is not good, nor to strike princes for equity. Prov 17:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The discretion of a man deferreth his anger; and it is his glory to pass over a transgression. Prov 19:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man of great wrath shall suffer punishment: for if thou deliver him, yet thou must do it again. Prov 19:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Make no friendship with an angry man; and with a furious man thou shalt not go: Prov 22:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Lest thou learn his ways, and get a snare to thy soul. Prov 22:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;By long forbearing is a prince persuaded, and a soft tongue breaketh the bone. Prov 25:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that hath no rule over his own spirit is like a city that is broken down, and without walls. Prov 25:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As coals are to burning coals, and wood to fire; so is a contentious man to kindle strife. Prov 26:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;An angry man stirreth up strife, and a furious man abound-eth in transgression. Prov 29:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-melancholy-and-cheerfulness"&gt;Of melancholy and cheerfulness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Heaviness in the heart of man maketh it stoop: but a good word maketh it glad. Prov 12:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The heart knoweth his own bitterness; and a stranger doth not intermeddle with his joy. Prov 14:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Even in laughter the heart is sorrowful; and the end of that mirth is heaviness. Prov 14:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A merry heart maketh a cheerful countenance: but by sorrow of the heart the spirit is broken. Prov 15:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;All the days of the afflicted are evil: but he that is of a merry heart hath a continual feast. Prov 15:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A merry heart doeth good like a medicine: but a broken spirit drieth the bones. Prov 17:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The spirit of a man will sustain his infirmity; but a wounded spirit who can bear? Prov 18:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As he that taketh away a garment in cold weather, and as vinegar upon nitre, so is he that singeth songs to an heavy heart. Prov 25:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As cold waters to a thirsty soul, so is good news from a far country. Prov 25:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-hope-and-expectation"&gt;Of hope and expectation&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Hope deferred maketh the heart sick: but when the desire cometh, it is a tree of life. Prov 13:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The desire accomplished is sweet to the soul: but it is abomination to fools to depart from evil. Prov 13:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-prudence-and-foolishness"&gt;Of prudence and foolishness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Every prudent man dealeth with knowledge: but a fool layeth open his folly. Prov 13:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The wisdom of the prudent is to understand his way: but the folly of fools is deceit. Prov 14:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The simple inherit folly: but the prudent are crowned with knowledge. Prov 14:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Wisdom resteth in the heart of him that hath understanding: but that which is in the midst of fools is made known. Prov 14:33&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The heart of him that hath understanding seeketh knowledge: but the mouth of fools feedeth on foolishness. Prov 15:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Folly is joy to him that is destitute of wisdom: but a man of understanding walketh uprightly. Prov 15:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The wise in heart shall be called prudent: and the sweetness of the lips increaseth learning. Prov 16:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Understanding is a wellspring of life unto him that hath it: but the instruction of fools is folly. Prov 16:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Wisdom is before him that hath understanding; but the eyes of a fool are in the ends of the earth. Prov 17:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A fool hath no delight in understanding, but that his heart may discover itself. Prov 18:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The heart of the prudent getteth knowledge; and the ear of the wise seeketh knowledge. Prov 18:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Through wisdom is an house builded; and by understanding it is established: And by knowledge shall the chambers be filled with all precious and pleasant riches. A wise man is strong; yea, a man of knowledge increaseth strength. For by wise counsel thou shalt make thy war: and in multitude of counsellors there is safety. Wisdom is too high for a fool: he openeth not his mouth in the gate. Prov 24:3-7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Her house is the way to hell, going down to the chambers of death. Prov 7:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that sendeth a message by the hand of a fool cutteth off the feet, and drinketh damage. The legs of the lame are not equal: so is a parable in the mouth of fools. As he that bindeth a stone in a sling, so is he that giveth honour to a fool. As a thorn goeth up into the hand of a drunkard, so is a parable in the mouth of fools. The great God that formed all things both rewardeth the fool, and rewardeth transgressors. As a dog returneth to his vomit, so a fool returneth to his folly. Prov 26:6-11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Evil men understand not judgment: but they that seek the LORD understand all things. Prov 28:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-treachery-and-fidelity"&gt;Of treachery and fidelity&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A wicked messenger falleth into mischief: but a faithful ambassador is health. Prov 13:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As the cold of snow in the time of harvest, so is a faithful messenger to them that send him: for he refresheth the soul of his masters. Prov 25:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Confidence in an unfaithful man in time of trouble is like a broken tooth, and a foot out of joint. Prov 25:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-good-and-bad-company"&gt;Of good and bad company&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that walketh with wise men shall be wise: but a companion of fools shall be destroyed. Prov 13:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Go from the presence of a foolish man, when thou perceivest not in him the lips of knowledge. Prov 14:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso keepeth the law is a wise son: but he that is a companion of riotous men shameth his father. Prov 28:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso loveth wisdom rejoiceth his father: but he that keepeth company with harlots spendeth his substance. Prov 29:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-the-education-of-children"&gt;Of the education of children&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that spareth his rod hateth his son: but he that loveth him chasteneth him betimes. Prov 13:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Chasten thy son while there is hope, and let not thy soul spare for his crying. Prov 19:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Even a child is known by his doings, whether his work be pure, and whether it be right. Prov 20:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Train up a child in the way he should go: and when he is old, he will not depart from it. Prov 22:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Foolishness is bound in the heart of a child; but the rod of correction shall drive it far from him. Prov 22:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Apply thine heart unto instruction, and thine ears to the words of knowledge. Prov 23:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The backslider in heart shall be filled with his own ways: and a good man shall be satisfied from himself. Prov 14:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The rod and reproof give wisdom: but a child left to himself bringeth his mother to shame. Prov 29:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Correct thy son, and he shall give thee rest; yea, he shall give delight unto thy soul. Prov 29:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-the-fear-of-the-lord"&gt;Of the fear of the Lord&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that walketh in his uprightness feareth the LORD: but he that is perverse in his ways despiseth him. Prov 14:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;In the fear of the LORD is strong confidence: and his children shall have a place of refuge. Prov 14:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The fear of the LORD is a fountain of life, to depart from the snares of death. Prov 14:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Better is little with the fear of the LORD than great treasure and trouble therewith. Prov 15:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The fear of the LORD is the instruction of wisdom; and before honour is humility. Prov 15:33&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;By mercy and truth iniquity is purged: and by the fear of the LORD men depart from evil. Prov 16:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The fear of the LORD tendeth to life: and he that hath it shall abide satisfied; he shall not be visited with evil. Prov 19:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;By humility and the fear of the LORD are riches, and honour, and life. Prov 22:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Let not thine heart envy sinners: but be thou in the fear of the LORD all the day long. Prov 23:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For surely there is an end; and thine expectation shall not be cut off. Prov 23:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-true-and-false-witness-bearing"&gt;Of true and false witness-bearing&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A faithful witness will not lie: but a false witness will utter lies. Prov 14:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A true witness delivereth souls: but a deceitful witness speaketh lies. Prov 14:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A false witness shall not be unpunished, and he that speaketh lies shall not escape. Prov 19:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A false witness shall not be unpunished, and he that speaketh lies shall perish. Prov 19:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;An ungodly witness scorneth judgment: and the mouth of the wicked devoureth iniquity. Prov 19:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A false witness shall perish: but the man that heareth speaketh constantly. Prov 21:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Be not a witness against thy neighbour without cause; and deceive not with thy lips. Prov 24:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man that beareth false witness against his neighbour is a maul, and a sword, and a sharp arrow. Prov 25:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-scorners"&gt;Of scorners&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A scorner seeketh wisdom, and findeth it not: but knowledge is easy unto him that understandeth. Prov 14:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Fools make a mock at sin: but among the righteous there is favour. Prov 14:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Proud and haughty scorner is his name, who dealeth in proud wrath. Prov 21:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Cast out the scorner, and contention shall go out; yea, strife and reproach shall cease. Prov 22:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The thought of foolishness is sin: and the scorner is an abomination to men. Prov 24:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;If a wise man contendeth with a foolish man, whether he rage or laugh, there is no rest. Prov 29:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-credulity-and-caution"&gt;Of credulity and caution&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The simple believeth every word: but the prudent man looketh well to his going. Prov 14:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A wise man feareth, and departeth from evil: but the fool rageth, and is confident. Prov 14:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A prudent man foreseeth the evil, and hideth himself; but the simple pass on, and are punished. Prov 27:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-kings-and-their-subjects"&gt;Of kings and their subjects&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;In the multitude of people is the king&amp;rsquo;s honour: but in the want of people is the destruction of the prince. Prov 14:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Righteousness exalteth a nation: but sin is a reproach to any people. Prov 14:34&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The king&amp;rsquo;s favour is toward a wise servant: but his wrath is against him that causeth shame. Prov 14:35&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A divine sentence is in the lips of the king: his mouth transgresseth not in judgment. Prov 16:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;It is an abomination to kings to commit wickedness: for the throne is established by righteousness. Righteous lips are the delight of kings; and they love him that speaketh right. The wrath of a king is as messengers of death: but a wise man will pacify it. In the light of the king&amp;rsquo;s countenance is life; and his favour is as a cloud of the latter rain. Prov 16:12-15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Many will intreat the favour of the prince: and every man is a friend to him that giveth gifts. Prov 19:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The king&amp;rsquo;s wrath is as the roaring of a lion; but his favour is as dew upon the grass. Prov 19:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The fear of a king is as the roaring of a lion: whoso provoketh him to anger sinneth against his own soul. Prov 20:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A king that sitteth in the throne of judgment scattereth away all evil with his eyes. Prov 20:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A wise king scattereth the wicked, and bringeth the wheel over them. Prov 20:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Mercy and truth preserve the king: and his throne is upholden by mercy. Prov 20:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that loveth pureness of heart, for the grace of his lips the king shall be his friend. Prov 22:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;These things also belong to the wise. It is not good to have respect of persons in judgment. He that saith unto the wicked, Thou art righteous; him shall the people curse, nations shall abhor him: But to them that rebuke him shall be delight, and a good blessing shall come upon them. Prov 24:23-25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Surely I am more brutish than any man, and have not the understanding of a man. I neither learned wisdom, nor have the knowledge of the holy. Who hath ascended up into heaven, or descended? who hath gathered the wind in his fists? who hath bound the waters in a garment? who hath established all the ends of the earth? what is his name, and what is his son&amp;rsquo;s name, if thou canst tell? Every word of God is pure: he is a shield unto them that put their trust in him. Prov 30:2-5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For the transgression of a land many are the princes thereof: but by a man of understanding and knowledge the state thereof shall be prolonged. Prov 28:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A poor man that oppresseth the poor is like a sweeping rain which leaveth no food. Prov 28:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As a roaring lion, and a ranging bear; so is a wicked ruler over the poor people. Prov 28:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The prince that wanteth understanding is also a great oppressor: but he that hateth covetousness shall prolong his days. Prov 28:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man that flattereth his neighbour spreadeth a net for his feet. Prov 29:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;If a ruler hearken to lies, all his servants are wicked. Prov 29:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The king that faithfully judgeth the poor, his throne shall be established for ever. Prov 29:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Many seek the ruler&amp;rsquo;s favour; but every man&amp;rsquo;s judgment cometh from the LORD. Prov 29:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-envy-especially-envying-sinners"&gt;Of envy, especially envying sinners&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A sound heart is the life of the flesh: but envy the rottenness of the bones. Prov 14:30&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Let not thine heart envy sinners: but be thou in the fear of the LORD all the day long. Prov 23:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For surely there is an end; and thine expectation shall not be cut off. Prov 23:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Be not thou envious against evil men, neither desire to be with them. Prov 24:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For their heart studieth destruction, and their lips talk of mischief. Prov 24:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Fret not thyself because of evil men, neither be thou envious at the wicked; Prov 24:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For there shall be no reward to the evil man; the candle of the wicked shall be put out. Prov 24:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Wrath is cruel, and anger is outrageous; but who is able to stand before envy? Prov 27:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-god-s-omniscience-and-his-universal-providence"&gt;Of God&amp;rsquo;s omniscience, and his universal providence&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The eyes of the LORD are in every place, beholding the evil and the good. Prov 15:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Hell and destruction are before the LORD: how much more then the hearts of the children of men? Prov 15:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The preparations of the heart in man, and the answer of the tongue, is from the LORD. Prov 16:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The LORD hath made all things for himself: yea, even the wicked for the day of evil. Prov 16:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man&amp;rsquo;s heart deviseth his way: but the LORD directeth his steps. Prov 16:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The lot is cast into the lap; but the whole disposing thereof is of the LORD. Prov 16:33&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The fining pot is for silver, and the furnace for gold: but the LORD trieth the hearts. Prov 17:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;There are many devices in a man&amp;rsquo;s heart; nevertheless the counsel of the LORD, that shall stand. Prov 19:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The hearing ear, and the seeing eye, the LORD hath made even both of them. Prov 20:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Man&amp;rsquo;s goings are of the LORD; how can a man then understand his own way? Prov 20:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The king&amp;rsquo;s heart is in the hand of the LORD, as the rivers of water: he turneth it whithersoever he will. Prov 21:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;There is no wisdom nor understanding nor counsel against the LORD. Prov 21:30&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The horse is prepared against the day of battle: but safety is of the LORD. Prov 21:31&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Many seek the ruler&amp;rsquo;s favour; but every man&amp;rsquo;s judgment cometh from the LORD. Prov 29:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-a-good-and-ill-name"&gt;Of a good and ill name&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The light of the eyes rejoiceth the heart: and a good report maketh the bones fat. Prov 15:30&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A GOOD name is rather to be chosen than great riches, and loving favour rather than silver and gold. Prov 22:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-men-s-good-opinion-of-themselves"&gt;Of men&amp;rsquo;s good opinion of themselves&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;There is a way which seemeth right unto a man, but the end thereof are the ways of death. Prov 14:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;All the ways of a man are clean in his own eyes; but the LORD weigheth the spirits. Prov 16:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;There is a way that seemeth right unto a man, but the end thereof are the ways of death. Prov 16:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Most men will proclaim every one his own goodness: but a faithful man who can find? Prov 20:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Every way of a man is right in his own eyes: but the LORD pondereth the hearts. Prov 21:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Seest thou a man wise in his own conceit? there is more hope of a fool than of him. Prov 26:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that trusteth in his own heart is a fool: but whoso walketh wisely, he shall be delivered. Prov 28:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-devotion-towards-god-and-dependence-on-him"&gt;Of devotion towards God, and dependence on him&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Commit thy works unto the LORD, and thy thoughts shall be established. Prov 16:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The name of the LORD is a strong tower: the righteous runneth into it, and is safe. Prov 18:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;My son, give me thine heart, and let thine eyes observe my ways. Prov 23:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Boast not thyself of to morrow; for thou knowest not what a day may bring forth. Prov 27:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that is of a proud heart stirreth up strife: but he that putteth his trust in the LORD shall be made fat. Prov 28:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The fear of man bringeth a snare: but whoso putteth his trust in the LORD shall be safe. Prov 29:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-the-happiness-of-god-s-favor"&gt;Of the happiness of God&amp;rsquo;s favor&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;When a man&amp;rsquo;s ways please the LORD, he maketh even his enemies to be at peace with him. Prov 16:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Many seek the ruler&amp;rsquo;s favour; but every man&amp;rsquo;s judgment cometh from the LORD. Prov 29:26&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="excitements-to-get-wisdom"&gt;Excitements to get wisdom&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;How much better is it to get wisdom than gold! and to get understanding rather to be chosen than silver! Prov 16:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;THROUGH desire a man, having separated himself, seeketh and intermeddleth with all wisdom. Prov 18:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that getteth wisdom loveth his own soul: he that keepeth understanding shall find good. Prov 19:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Hear counsel, and receive instruction, that thou mayest be wise in thy latter end. Prov 19:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Bow down thine ear, and hear the words of the wise, and apply thine heart unto my knowledge. For it is a pleasant thing if thou keep them within thee; they shall withal be fitted in thy lips. That thy trust may be in the LORD, I have made known to thee this day, even to thee. Have not I written to thee excellent things in counsels and knowledge, That I might make thee know the certainty of the words of truth; that thou mightest answer the words of truth to them that send unto thee? Prov 22:17-21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;My son, if thine heart be wise, my heart shall rejoice, even mine. Prov 23:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Yea, my reins shall rejoice, when thy lips speak right things. Prov 23:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Hearken unto thy father that begat thee, and despise not thy mother when she is old. Buy the truth, and sell it not; also wisdom, and instruction, and understanding. The father of the righteous shall greatly rejoice: and he that begetteth a wise child shall have joy of him. Thy father and thy mother shall be glad, and she that bare thee shall rejoice. Prov 23:22-25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;My son, eat thou honey, because it is good; and the honeycomb, which is sweet to thy taste: Prov 24:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;So shall the knowledge of wisdom be unto thy soul: when thou hast found it, then there shall be a reward, and thy expectation shall not be cut off. Prov 24:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;My son, be wise, and make my heart glad, that I may answer him that reproacheth me. Prov 27:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="cautions-against-temptations"&gt;Cautions against temptations&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The highway of the upright is to depart from evil: he that keepeth his way preserveth his soul. Prov 16:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;An unjust man is an abomination to the just: and he that is upright in the way is abomination to the wicked. Prov 29:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-old-age-and-youth"&gt;Of old age and youth&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The hoary head is a crown of glory, if it be found in the way of righteousness. Prov 16:31&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Children&amp;rsquo;s children are the crown of old men; and the glory of children are their fathers. Prov 17:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The glory of young men is their strength: and the beauty of old men is the gray head. Prov 20:29&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-servants"&gt;Of servants&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A wise servant shall have rule over a son that causeth shame, and shall have part of the inheritance among the brethren. Prov 17:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Delight is not seemly for a fool; much less for a servant to have rule over princes. Prov 19:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A servant will not be corrected by words: for though he understand he will not answer. Prov 29:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that delicately bringeth up his servant from a child shall have him become his son at the length. Prov 29:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-bribery"&gt;Of bribery&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A gift is as a precious stone in the eyes of him that hath it: whithersoever it turneth, it prospereth. Prov 17:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A wicked man taketh a gift out of the bosom to pervert the ways of judgment. Prov 17:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man&amp;rsquo;s gift maketh room for him, and bringeth him before great men. Prov 18:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A gift in secret pacifieth anger: and a reward in the bosom strong wrath. Prov 21:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;To have respect of persons is not good: for for a piece of bread that man will transgress. Prov 28:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-reproof-and-correction"&gt;Of reproof and correction&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A reproof entereth more into a wise man than an hundred stripes into a fool. Prov 17:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Smite a scorner, and the simple will beware: and reprove one that hath understanding, and he will understand knowledge. Prov 19:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Judgments are prepared for scorners, and stripes for the back of fools. Prov 19:29&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The blueness of a wound cleanseth away evil: so do stripes the inward parts of the belly. Prov 20:30&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;When the scorner is punished, the simple is made wise: and when the wise is instructed, he receiveth knowledge. Prov 21:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As an earring of gold, and an ornament of fine gold, so is a wise reprover upon an obedient ear. Prov 25:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A whip for the horse, a bridle for the ass, and a rod for the fool&amp;rsquo;s back. Prov 26:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Open rebuke is better than secret love. Prov 27:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Faithful are the wounds of a friend; but the kisses of an enemy are deceitful. Prov 27:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Though thou shouldest bray a fool in a mortar among wheat with a pestle, yet will not his foolishness depart from him. Prov 27:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that rebuketh a man afterwards shall find more favour than he that flattereth with the tongue. Prov 28:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He, that being often reproved hardeneth his neck, shall suddenly be destroyed, and that without remedy. Prov 29:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-ingratitude"&gt;Of ingratitude&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso rewardeth evil for good, evil shall not depart from his house. Prov 17:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-friendship"&gt;Of friendship&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A friend loveth at all times, and a brother is born for adversity. Prov 17:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man that hath friends must shew himself friendly: and there is a friend that sticketh closer than a brother. Prov 18:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Ointment and perfume rejoice the heart: so doth the sweetness of a man&amp;rsquo;s friend by hearty counsel. Prov 27:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Thine own friend, and thy father&amp;rsquo;s friend, forsake not; neither go into thy brother&amp;rsquo;s house in the day of thy calamity: for better is a neighbour that is near than a brother far off. Prov 27:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that blesseth his friend with a loud voice, rising early in the morning, it shall be counted a curse to him. Prov 27:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Iron sharpeneth iron; so a man sharpeneth the countenance of his friend. Prov 27:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-sensual-pleasures"&gt;Of sensual pleasures&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that loveth pleasure shall be a poor man: he that loveth wine and oil shall not be rich. Prov 21:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;When thou sittest to eat with a ruler, consider diligently what is before thee: And put a knife to thy throat, if thou be a man given to appetite. Be not desirous of his dainties: for they are deceitful meat. Prov 23:1-3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Eat thou not the bread of him that hath an evil eye, neither desire thou his dainty meats: For as he thinketh in his heart, so is he: Eat and drink, saith he to thee; but his heart is not with thee. The morsel which thou hast eaten shalt thou vomit up, and lose thy sweet words. Prov 23:6-8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Hear thou, my son, and be wise, and guide thine heart in the way. Be not among winebibbers; among riotous eaters of flesh: For the drunkard and the glutton shall come to poverty: and drowsiness shall clothe a man with rags. Prov 23:19-21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The full soul loatheth an honeycomb; but to the hungry soul every bitter thing is sweet. Prov 27:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-drunkenness"&gt;Of drunkenness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;WINE is a mocker, strong drink is raging: and whosoever is deceived thereby is not wise. Prov 20:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Buy the truth, and sell it not; also wisdom, and instruction, and understanding. Prov 23:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Who hath woe? who hath sorrow? who hath contentions? who hath babbling? who hath wounds without cause? who hath redness of eyes? They that tarry long at the wine; they that go to seek mixed wine. Look not thou upon the wine when it is red, when it giveth his colour in the cup, when it moveth itself aright. At the last it biteth like a serpent, and stingeth like an adder.  Thine eyes shall behold strange women, and thine heart shall utter perverse things. Yea, thou shalt be as he that lieth down in the midst of the sea, or as he that lieth upon the top of a mast. They have stricken me, shalt thou say, and I was not sick; they have beaten me, and I felt it not: when shall I awake? I will seek it yet again. Prov 23:29-35&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-the-universal-corruption-of-nature"&gt;Of the universal corruption of nature&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Who can say, I have made my heart clean, I am pure from my sin? Prov 20:9&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-flattery"&gt;Of flattery&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that goeth about as a talebearer revealeth secrets: therefore meddle not with him that flattereth with his lips. Prov 20:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A lying tongue hateth those that are afflicted by it; and a flattering mouth worketh ruin. Prov 26:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that rebuketh a man afterwards shall find more favour than he that flattereth with the tongue. Prov 28:23&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;A man that flattereth his neighbour spreadeth a net for his feet. Prov 29:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-undutiful-children"&gt;Of undutiful children&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso curseth his father or his mother, his lamp shall be put out in obscure darkness. Prov 20:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Whoso robbeth his father or his mother, and saith, It is no transgression; the same is the companion of a destroyer. Prov 28:24&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-the-short-continuance-of-what-is-ill-gotten"&gt;Of the short continuance of what is ill-gotten&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;An inheritance may be gotten hastily at the beginning; but the end thereof shall not be blessed. Prov 20:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The getting of treasures by a lying tongue is a vanity tossed to and fro of them that seek death. Prov 21:6&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The robbery of the wicked shall destroy them; because they refuse to do judgment. Prov 21:7&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that soweth iniquity shall reap vanity: and the rod of his anger shall fail. Prov 22:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that by usury and unjust gain increaseth his substance, he shall gather it for him that will pity the poor. Prov 28:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-revenge"&gt;Of revenge&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Say not thou, I will recompense evil; but wait on the LORD, and he shall save thee. Prov 20:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Rejoice not when thine enemy falleth, and let not thine heart be glad when he stumbleth: Prov 24:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Lest the LORD see it, and it displease him, and he turn away his wrath from him. Prov 24:18&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Say not, I will do so to him as he hath done to me: I will render to the man according to his work. Prov 24:29&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-sacrilege"&gt;Of sacrilege&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;It is a snare to the man who devoureth that which is holy, and after vows to make enquiry. Prov 20:25&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-conscience"&gt;Of conscience&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The spirit of man is the candle of the LORD, searching all the inward parts of the belly. Prov 20:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;As in water face answereth to face, so the heart of man to man. Prov 27:19&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-the-preference-of-moral-duties-before-ceremonial"&gt;Of the preference of moral duties before ceremonial&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The sacrifice of the wicked is an abomination to the LORD: but the prayer of the upright is his delight. Prov 15:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;To do justice and judgment is more acceptable to the LORD than sacrifice. Prov 21:3&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;The sacrifice of the wicked is abomination: how much more, when he bringeth it with a wicked mind? Prov 21:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-prodigality-and-wastefulness"&gt;Of prodigality and wastefulness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;There is treasure to be desired and oil in the dwelling of the wise; but a foolish man spendeth it up. Prov 21:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="the-triumphs-of-wisdom-and-godliness"&gt;The triumphs of wisdom and godliness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A wise man scaleth the city of the mighty, and casteth down the strength of the confidence thereof. Prov 21:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Lay not wait, O wicked man, against the dwelling of the righteous; spoil not his resting place: Prov 24:15&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For a just man falleth seven times, and riseth up again: but the wicked shall fall into mischief. Prov 24:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-frowardness-and-tractableness"&gt;Of frowardness and tractableness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Thorns and snares are in the way of the froward: he that doth keep his soul shall be far from them. Prov 22:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-uncleanness"&gt;Of uncleanness&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The mouth of strange women is a deep pit: he that is abhorred of the LORD shall fall therein. Prov 22:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For a whore is a deep ditch; and a strange woman is a narrow pit. Prov 23:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;She also lieth in wait as for a prey, and increaseth the transgressors among men. Prov 23:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-fainting-in-affliction"&gt;Of fainting in affliction&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;If thou faint in the day of adversity, thy strength is small. Prov 24:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-helping-the-distressed"&gt;Of helping the distressed&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The house of the wicked shall be overthrown: but the tabernacle of the upright shall flourish. Prov 14:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;There is a way which seemeth right unto a man, but the end thereof are the ways of death. Prov 14:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-loyalty-to-the-government"&gt;Of loyalty to the government&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;My son, fear thou the LORD and the king: and meddle not with them that are given to change: Prov 24:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For their calamity shall rise suddenly; and who knoweth the ruin of them both? Prov 24:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-forgiving-enemies"&gt;Of forgiving enemies&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;If thine enemy be hungry, give him bread to eat; and if he be thirsty, give him water to drink: Prov 25:21&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;For thou shalt heap coals of fire upon his head, and the LORD shall reward thee. Prov 25:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-causeless-curse"&gt;Of causeless curse&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;As the bird by wandering, as the swallow by flying, so the curse causeless shall not come. Prov 26:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-answering-fools"&gt;Of answering fools&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Answer not a fool according to his folly, lest thou also be like unto him. Prov 26:4&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Answer a fool according to his folly, lest he be wise in his own conceit. Prov 26:5&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-unsettledness-and-dissatisfaction"&gt;Of unsettledness and dissatisfaction&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;As a bird that wandereth from her nest, so is a man that wandereth from his place. Prov 27:8&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Hell and destruction are never full; so the eyes of man are never satisfied. Prov 27:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-cowardliness-and-courage"&gt;Of cowardliness and courage&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The wicked flee when no man pursueth: but the righteous are bold as a lion. Prov 28:1&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="the-people-s-interest-in-the-character-of-their-rulers"&gt;The people&amp;rsquo;s interest in the character of their rulers&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;When righteous men do rejoice, there is great glory: but when the wicked rise, a man is hidden. Prov 28:12&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;When the wicked rise, men hide themselves: but when they perish, the righteous increase. Prov 28:28&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;When the righteous are in authority, the people rejoice: but when the wicked beareth rule, the people mourn. Prov 29:2&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;When the wicked are multiplied, transgression increaseth: but the righteous shall see their fall. Prov 29:16&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;When it goeth well with the righteous, the city rejoiceth: and when the wicked perish, there is shouting. Prov 11:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;By the blessing of the upright the city is exalted: but it is overthrown by the mouth of the wicked. Prov 11:11&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="the-benefit-of-repentance-and-holy-fear"&gt;The benefit of repentance and holy fear&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;He that covereth his sins shall not prosper: but whoso confesseth and forsaketh them shall have mercy. Prov 28:13&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;Happy is the man that feareth alway: but he that hardeneth his heart shall fall into mischief. Prov 28:14&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="the-punishment-of-murder"&gt;The punishment of murder&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A man that doeth violence to the blood of any person shall flee to the pit; let no man stay him. Prov 28:17&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="of-hastening-to-be-rich"&gt;Of hastening to be rich&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;A faithful man shall abound with blessings: but he that maketh haste to be rich shall not be innocent. Prov 28:20&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;He that hasteth to be rich hath an evil eye, and considereth not that poverty shall come upon him. Prov 28:22&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="the-enmity-of-the-wicked-against-the-godly"&gt;The enmity of the wicked against the godly&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;The bloodthirsty hate the upright: but the just seek his soul. Prov 29:10&lt;/p&gt;&lt;/blockquote&gt;
&lt;!--quoteend--&gt;
&lt;blockquote&gt;
&lt;p&gt;An unjust man is an abomination to the just: and he that is upright in the way is abomination to the wicked. Prov 29:27&lt;/p&gt;&lt;/blockquote&gt;
&lt;h2 id="the-necessity-of-the-means-of-grace"&gt;The necessity of the means of grace&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Where there is no vision, the people perish: but he that keepeth the law, happy is he. Prov 29:18&lt;/p&gt;&lt;/blockquote&gt;</description><author>Honza Pokorný</author><pubDate>Tue, 03 Sep 2024 18:00:00 GMT</pubDate><guid isPermaLink="true">https://honza.pokorny.ca/2024/09/proverbs-categorized/</guid></item><item><title>On this day, September  3</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-3/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2005 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Tue, 03 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-3/</guid></item><item><title>Is this trait sealed, or not sealed — that is the question</title><link>https://predr.ag/blog/is-this-trait-sealed-or-not-sealed/</link><description>&lt;p&gt;&lt;em&gt;&lt;a href="https://github.com/obi1kenobi/cargo-semver-checks/releases/tag/v0.35.0" rel="external"&gt;&lt;code&gt;cargo-semver-checks&lt;/code&gt; v0.35&lt;/a&gt; can determine whether Rust traits are "sealed", allowing it to catch many tricky new instances of SemVer breakage. Why is accurate sealed trait detection so important, and why is implementing it correctly so hard?&lt;/em&gt;&lt;/p&gt;</description><author>Predrag Gruevski's blog and personal site.</author><pubDate>Tue, 03 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://predr.ag/blog/is-this-trait-sealed-or-not-sealed/</guid></item><item><title>Setting up Mainsail on Ender 3 V3 KE</title><link>https://heitorpb.github.io/bla/ender3v3ke-mainsail/</link><description>How to setup Mainsail and webcam support on Creality Ender 3 V3 KE printer.</description><author>Heitor's log</author><pubDate>Tue, 03 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://heitorpb.github.io/bla/ender3v3ke-mainsail/</guid></item><item><title>Roughly Everything You Need to Know About Entity Resolution</title><link>https://faingezicht.com/articles/2024/09/03/entity-resolution/</link><description>Naming things is hard. This is not just a technical problem, but a foundational problem that we have as humans when we talk to each other and refer to specific things and people. I have four good friends named Zach – there's a reason we invented nicknames. 

There are many other such name collisions in every domain. Companies and products can share names, giving rise to trademarks to limit the usage of a given label within a domain. There are multiple YC companies called *Alpha* or *Level*. On the other hand, names can also be divergent. My previous employer, Vouch, showed up in some places as Vouch Inc., and Vouch Insurance in others. The company also used to be called SV InsureTech, but most often is just referred to as Vouch. 

Entity resolution (ER) is the process of identifying and linking multiple references to the same real-world entity across various data sources. In some businesses, de-duplicating and linking data correctly is existential. If you are a bank handing out loans, or an insurance company underwriting a new policy, you need to know who you are dealing with – not just for the sake of your margins but also to comply with regulatory mandates. It's not uncommon for a single entity to have multiple names, addresses, etc., which makes selling &lt;a href="https://en.wikipedia.org/wiki/Anti%E2%80%93money_laundering"&gt;Anti-Money Laundering&lt;/a&gt; (AML) and &lt;a href="https://en.wikipedia.org/wiki/Know_your_customer"&gt;Know Your Customer&lt;/a&gt; (KYC) software a big business. 

Leaning on my experience designing the data pipelines that created and maintained Vouch's knowledge graph of the startup ecosystem, in this blog post, I'll discuss some of the key ideas behind ER systems, and the challenges in building them.

**The Process**  
Imagine you are a home goods retailer reviewing product catalogs from suppliers. The items on offer have different names, wordy descriptions, some information about their manufacturers, and catalog-specific identifiers. In order to find the cheapest supplier for each item, say a dishwasher, you or your teammates could manually compare models across the catalogs. You'd work your way through them, maybe by matching manufacturer names first and looking at similarities between features later, comparing prices along the way. However, this process becomes impractical as the number of records and catalogs increase. If two catalogs contain 1000 dishwashers each, then there are 1 million pairs to examine, and that’s just operating on two relatively small sets of inputs. As more and larger catalogs are introduced, the task becomes too complex to be done manually. ER algorithms reduce the amount of work we need to do to establish canonical entities systematically and at scale.

Most ER systems follow a similar process.

1. Schema Alignment  
2. Cleaning/Standardization  
3. Blocking  
4. Matching/Linking  
   4a. Adding deterministic links  
5. Assigning virtual IDs  
6. Canonicalization/Summarization  
7. Evaluation

Let's break down each of these steps.

### 1. Schema Alignment
Like in most data problems, we start with messy data. Schema alignment is the process of mapping and standardizing data fields from different sources to a common set of attributes. For example, one of your appliance datasets might use `item_name` while another uses `model_name` - schema alignment would recognize these as equivalent and map them to a standardized field name. In some cases, a column might have to be split into multiple fields. For example, a person's name might be split into first, middle, and last names, or a manufacturer's name might be split into its legal name and its "doing business as" (DBA) name.  

&lt;img alt="Schema alignment" src="https://cdn.faingezicht.com/er/er1.png" /&gt;

The idea here is to end up with a single schema into which we can aggregate every record into, covering all sources, even if there might be some holes across datasets when there isn't a perfect column overlap. In the case where you have a single dataset with duplicates, which is referred to as a "clean" ER task, this step is not necessary.

### 2. Cleaning and standardization
Once we know which fields to compare, we need to ensure that they follow the same formats, or if they are numerical that they are in the same units of measurement. For example, the price column in one dataset might be in US Dollars while another is in Euros or, more subtly, one includes tax while another doesn't. Both of these cases require normalization. If working with dates, you want to parse them to all be in a single format (e.g., `YYYY-MM-DD`), or if working with URLs perhaps you want to cut them down to just the &lt;a href="https://en.wikipedia.org/wiki/Fully_qualified_domain_name"&gt;fully qualified domain name&lt;/a&gt;, removing subdomains and paths. Other strings might require removing punctuation, converting to lowercase, etc.  

#### Mappings

| Source    | Column Name        | Mapped Name             |
|-----------|--------------------|-------------------------|
| Catalog 1 | ID                 | source_id               |
| Catalog 1 | Manufacturer ID     | manufacturer_product_id |
| Catalog 1 | Manufacturer        | manufacturer_name       |
| Catalog 1 | Name               | product_name            |
| Catalog 1 | MSRP               | price_usd               |
| Catalog 2 | ID                 | source_id               |
| Catalog 2 | Brand              | manufacturer_name       |
| Catalog 2 | Product Name       | product_name            |
| Catalog 2 | Price (Euros)      | price_usd               |

Making sure data is comparable apples to apples helps with matching, in part because asserting equality is computationally much cheaper than fuzzy matching, but also because it ensures that the final dataset you generate is internally consistent.  

#### Consolidated Table

| Source   | source_id    | mfg_name          | product_name                          | mfg_product_id          | price_usd_cents |
|----------|--------------|-------------------|---------------------------------------|-------------------------|-----------------|
| Cat. 1   | 5678924      | Black &amp; Decker    | Honeycomb™ Collection 2-Slice Toaster | TR1250WD1               | 5499            |
| Cat. 1   | 5674678      | Black and Decker  | 2-Slice Toaster                       | TR7827-1BD              | 1999            |
| Cat. 1   | 5678738      | Black &amp; Decker    | 2-Slice Rapid Toaster                 | TR3501BS                | 4399            |
| Cat. 2   | a8adfd1378b6 | BLACKANDDECKER    | Honeycomb 2-Slice Toaster             |                         | 5250            |
| Cat. 2   | ca0b07325c8a | BLACKANDDECKER    | 2 Slice Toaster                       |                         | 1838            |
| Cat. 2   | 021b2c3bd759 | BLACKANDDECKER    | Two Slice Rapid Toaster               |                         | 3386            |

### 3. Blocking
As previously mentioned, one of the biggest problems with naive ER is the sheer number of comparisons that need to be made. This is what computer scientists would call an *N^2 problem*, where we'd compare every record to every other record. Blocking is how we reduce the search space by creating a smaller set of candidate pairs to compare. Specifically, the goal is to do a single pass on the dataset and assign each record a "blocking key" using only the information available on that record. In some cases, you might want to create a blocking key per attribute, like a company name or website, and in others, you might want to create a hybrid key that combines multiple attributes. Notice that you should not use the source of your data as a blocking key – nothing assures you that there aren't duplicate rows within a given input source.

On each box below, every row and column represents an entity, and every cell a comparison. On the leftmost we are doing every possible comparison. On the second, we have blocked the entities based on some rule into six distinct groups. Since we don't have to compare the entities to themselves, the third box removes those as well, and lastly since the comparisons are symmetric we only need to do them once per pair.

&lt;img alt="Blocking" src="https://cdn.faingezicht.com/er/er4.png" style="width: 100%;" /&gt;
Blocking can be as simple as picking an existing categorical column to block on, or as complex as using a machine learning model to assign records to clusters. You probably block your socks when you're folding laundry, making separate piles by color and then matching corresponding pairs. Looking back to the retail catalog example, we could compare fridges to fridges and toasters to toasters, without comparing fridges to toasters. That works if every item is correctly labeled as one or the other, but sometimes we don't have such reliable categories. When working with businesses or people data we often have to get more creative. On the simpler side we could use the first letter of a name column for blocking. If we're concerned with typos, or variable spellings, we might pipe the data through a phonetic indexing algorithm like &lt;a href="https://en.wikipedia.org/wiki/Soundex"&gt;Soundex&lt;/a&gt;. If the attributes we are blocking on are numerical (like age or revenue) we can divide them into discrete bins and block records within the same bin. All of these are simple solutions, but on the other end of the spectrum complex clustering algorithms like &lt;a href="https://en.wikipedia.org/wiki/Locality-sensitive_hashing"&gt;locality sensitive hashing&lt;/a&gt; or embeddings can help us bucket records, too. We might want to take into account multiple attributes to generate blocks, combining methods, but custom rules based on domain knowledge are often effective enough.

Each of the methods has trade-offs in computational complexity, accuracy, and explainability. The goal at this stage is to find a balance that works for your data while making the process faster by reducing the number of comparisons to be made.

While it might be tempting to use hybrid blocking and aggregate various fields into a single blocking key, the more complex the blocking keys you use the harder it is to understand and debug your system. I've found that the simpler comparison of one key at a time was a better approach, even if it required having multiple sets of blocking keys and more comparisons (one per attribute, plus a few smaller hybrid ones).

### 4. Matching and Linking
Once we have blocked the records, we iterate through the blocks and compare each pair of attributes or attribute groups within a block to decide if they are the same or not. Here, too, you can do something as simple as an equality check, which works for primary key-like values domain names or SSNs, or build a more complex model to account for typos, transcription errors, and other noise. Ultimately we are creating a binary classification model that takes a subset of the attributes of the record to predict if they are the same or not. Different measures can be used to process different attribute blocks, like using string a similarity metric like &lt;a href="https://en.wikipedia.org/wiki/Levenshtein_distance"&gt;Levenshtein distance&lt;/a&gt; for names or &lt;a href="https://en.wikipedia.org/wiki/Jaccard_index"&gt;Jaccard similarity&lt;/a&gt; to compare associated groups (say lists of friends on a social network, or a list of investments). You could even feed these to an LLM. Either way, you'll have to decide on a threshold for creating a link or not, which leads to similar trade-offs to those we saw when discussing blocking. Beyond simple rules, decision trees or logistic regression models are often good starting points.

This is an area where introducing domain knowledge is high leverage. For example, if you turned URLs into root domains before comparison, you might have two records whose links previously pointed to different apps on the App Store now both pointing to `apple.com`. The same problem comes up with GitHub links, social media accounts, etc. Creating hard coded rules to ignore these shared paths will improve the quality of your matches, but it also introduces new edge cases: the entity `Apple` can't be matched properly anymore, which could be a problem if you were trying to include all public companies in your dataset. On a similar vein, you should consider how to deal with subsidiaries, or related entities – should you match `amazon.com` with `amazon.co.uk`? Your business needs will guide you to different business logic.

### 4a. Adding deterministic links
In some cases, we can take advantage of already existing deterministic links between records. If your application code creates a record in an internal system like Salesforce or Zendesk, you can pass along the identifiers at creation-time to link records later in your ER system. If working with data about startups or SMBs you could have an offline process that follows DNS redirects offline, tracking that a URL like `usecompany.com` now redirects to `company.com`. These links are not based on the comparison of attributes, but on the knowledge you bake directly into the system.

Whether the links came from a probabilistic comparison system, or from a deterministic process, their downstream use is the same. The output of this step is a sequence of `(A, same_as, B)` triples, linking two records. In aggregate, all these links form a graph where each connected component represents N records. If our process works well we can assume, through transitivity, that each subcomponent represents a single real world entity. If you wanted to get fancy, you could assign each link a weight based on the confidence of the match, giving deterministic links a 100% confidence and get yet another threshold to tune on other links to decide if a link should be created or not.

### 5. Assigning virtual IDs
&lt;img alt="Virtual ID clusters" src="https://cdn.faingezicht.com/er/er5.png" style="width: 400px;" /&gt;

At this point, we have a graph of linked records with many disconnected subgraphs. By performing a simple graph traversal, we can now assign a unique virtual identifier (VID) to each connected component. This could be a random UUID, but since a long lived system benefits from semi-stable identifiers we can do better. Hashing the attributes of the oldest record in the cluster is one such approach. If the cluster is split, the older half retains the original ID, while the newer half gets a new ID. This ensures that the ID remains stable even as the data changes.

One process that you can use to improve your data quality is to manually review the largest clusters produced by the ER system, as well as any totally unmatched entities. Tracking cluster size distribution, and the specific entities that land on either tail, will allow you to correct errors in the generated data by identifying overbearing or missing rules, as well as thresholds that are too strict or too lenient. This is a labor-intensive process, but it can be very effective in improving the quality of the data.

### 6. Canonicalization/Summarization
With our data connected, we have to decide which attributes to keep. If we have two records from different sources glued together with a `same_as` now we have two values for each of their fields. For example, source one might say that company `X` has 100 employees while source two says it has 110 employees. Which one do we keep? The process of choosing the right attributes is called canonicalization or summarization. The goal is to create a single record that represents the entity as accurately as possible. This is an area where ML can help with outliers, but domain knowledge and business rules can carry a lot of weight. This is a hard problem, which Nabeel Zewail and I discussed at length in &lt;a href="https://www.youtube.com/watch?v=MTZD5VTV4NI"&gt;our talk at KGC'22, Modeling the Startup Ecosystem Using a Config Based Knowledge Graph&lt;/a&gt;.

Adding to the complexity, we might have to deal with the temporal aspect of the data. The system we devised kept a date associated with each fact, which blew up both storage and computational costs but which allowed us to present the state of a summarized record "as of" a certain date. In general, we presented summaries as of the most recent date, but we could also present the state of the record at any point in time by running canonicalization on the fly on a filtered subset of associated records to an object. Generating a materialized view of the latest data then allowed us to serve the data quickly and efficiently, making it joinable with other datasets.

### 7. Evaluation
Finally, we need to evaluate the quality of our ER system, which we can do with a mix of online and offline metrics. Online metrics are those that help us measure the actual performance the model had in a real world setting, while offline metrics evaluate performance on historical data during model development and testing. Since there is no ground truth, online metrics are harder to compute so we end up using proxies like the number of input records, the number of output records, and the number of links created, as well as ratios between them. Keeping track across runs can tell us quickly if the system is working as expected: If we see that a run generated a ton of new clusters, several very large clusters, or many stand-alone clusters, we can assume something went wrong. Running our model on a static human-evaluated dataset allows us to check our results offline, and by comparing the output of the model to what a human would have guessed we can get a more nuanced view of where we're underperforming. However, curating these datasets is expensive, and it might not be obvious what needs fixing. Instead of trying to evaluate the entire system at once, it's better to evaluate each sub-model in the process separately. This way, you can identify where the system is failing and make targeted improvements. The evaluation process is iterative, and should be done regularly to ensure that the system is still working as expected.

This article is a brief survey, and each section could be a blog post of its own. To go deeper, I recommend the following articles, which helped me get a better understanding of the problems:

* &lt;a href="https://towardsdatascience.com/practical-guide-to-entity-resolution-part-1-f7893402ea7e"&gt;Practical Guide to Entity Resolution,&lt;/a&gt; Yifei Huang, Palantir  
* &lt;a href="https://blog.acolyer.org/2020/12/14/entity-resolution/"&gt;An overview of end-to-end entity resolution for big data,&lt;/a&gt; Adrian Colyer, Accel  
* &lt;a href="https://unece.org/sites/default/files/2021-12/SDC2021_Day1_Steorts_P.pdf"&gt;(Almost) All of Entity Resolution&lt;/a&gt;, Rebecca C. Steorts, Duke University  
* &lt;a href="https://raw.githubusercontent.com/AI-team-UoA/pyJedAI/main/docs/presentations/ICWE2024/5gER-Tutorial-Slides.pdf"&gt;The Five Generations of Entity Resolution on Web Data&lt;/a&gt;,  Konstantinos Nikoletos, Ekaterini Ioannou, George Papadakis, ICWE 2024 

&lt;small&gt;Thanks to Hannah Doherty and Ben Warren for reading drafts of this post.&lt;/small&gt;

&lt;hr /&gt;

&lt;small&gt;
&lt;em&gt;Photo: Metropolitan Stickers, by me. Previously posted on &lt;a href="/photos/2018/05/10/nyc2018/"&gt;New York City, 2018&lt;/a&gt;.
&lt;/em&gt;&lt;/small&gt;</description><author>Avy Faingezicht</author><pubDate>Tue, 03 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://faingezicht.com/articles/2024/09/03/entity-resolution/</guid></item><item><title>Purpose and the future of work</title><link>https://www.umarniz.com/purpose-future-of-work/</link><description>A statistic that recently surprised me; Even though a lot of people feel the current job market is tough, there are approximately 1.2…</description><author>Umar Nizamani | RSS Feed</author><pubDate>Tue, 03 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.umarniz.com/purpose-future-of-work/</guid></item><item><title>Game theory at work: when to talk and when to shut up</title><link>https://swaits.com/game-theory-at-work-and-when-to-shutup/</link><description>&lt;p&gt;&lt;img alt="" src="https://swaits.com/game-theory-at-work-and-when-to-shutup/swaits-at-bookshelf.jpeg" /&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://swaits.com/nobody-tells-me-what-to-do/"&gt;Nobody tells me what to do at work&lt;/a&gt;. But
sometimes, I tell myself to shut up.&lt;/p&gt;
&lt;p&gt;Before we dive into the workplace, let's talk about &lt;a href="https://en.wikipedia.org/wiki/Game_theory" rel="external"&gt;game
theory&lt;/a&gt;. No, it's not about
videogames or &lt;a href="https://en.wikipedia.org/wiki/Eurogame" rel="external"&gt;board games&lt;/a&gt; like
&lt;a href="https://en.wikipedia.org/wiki/Catan" rel="external"&gt;Settlers of Catan&lt;/a&gt;. Game theory is the
study of strategic decision-making. It's a fancy way of saying, "If I do X, and
you do Y, what happens?"&lt;/p&gt;
&lt;h2 id="game-theory-101-the-ukraine-russia-conflict"&gt;Game Theory 101: The Ukraine-Russia Conflict&lt;/h2&gt;
&lt;p&gt;Let's look at a real-world example: &lt;a href="https://en.wikipedia.org/wiki/Russo-Ukrainian_War" rel="external"&gt;the ongoing conflict between Ukraine and
Russia, with NATO in the
mix&lt;/a&gt;. Don't worry, I'm not
getting political here - just using it as an example of game theory in action.&lt;/p&gt;
&lt;p&gt;Picture this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ukraine wants to maintain its independence and potentially join NATO.&lt;/li&gt;
&lt;li&gt;Russia wants to prevent NATO expansion and maintain influence over Ukraine.&lt;/li&gt;
&lt;li&gt;NATO wants to support Ukraine without triggering a larger conflict.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, let's break it down:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;If Ukraine joins NATO:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ukraine gains security (win)&lt;/li&gt;
&lt;li&gt;Russia loses influence (loss)&lt;/li&gt;
&lt;li&gt;NATO expands (win)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If Russia invades Ukraine:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ukraine loses territory (loss)&lt;/li&gt;
&lt;li&gt;Russia gains territory but faces sanctions (mixed)&lt;/li&gt;
&lt;li&gt;NATO is challenged to respond (complicated)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If NATO directly intervenes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ukraine might be saved (win)&lt;/li&gt;
&lt;li&gt;Russia is confronted (loss)&lt;/li&gt;
&lt;li&gt;Risk of a larger conflict increases (big potential loss for everyone)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;See how complicated this gets? Each player has to consider not just their own
moves, but how others might respond. That's game theory in a nutshell.&lt;/p&gt;
&lt;p&gt;Now, let's bring this back to something a bit less world-changing: your job.&lt;/p&gt;
&lt;h2 id="the-game-of-workplace-communication"&gt;The Game of Workplace Communication&lt;/h2&gt;
&lt;p&gt;Every time you open your mouth at work, you're playing a game. The stakes? Your
reputation, relationships, and sometimes even your job. The players? You, your
colleagues, your boss, your customers(!!!), and anyone else within earshot.&lt;/p&gt;
&lt;p&gt;Let's break down a few scenarios:&lt;/p&gt;
&lt;h3 id="scenario-1-the-political-powder-keg"&gt;Scenario 1. The Political Powder Keg&lt;/h3&gt;
&lt;p&gt;Should you talk about politics at work? HELL NO.&lt;/p&gt;
&lt;p&gt;Here's the game theory breakdown:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you talk and people agree: Small gain (maybe a nod of approval)&lt;/li&gt;
&lt;li&gt;If you talk and people disagree: Massive loss (damaged relationships,
potential HR issues)&lt;/li&gt;
&lt;li&gt;If you stay quiet: No loss, potential small gain (being seen as professional)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The matrix is clear: you have nothing to gain and lots to lose. Keep your
political views to yourself, folks.&lt;/p&gt;
&lt;p&gt;This is a universal rule. It doesn't matter if you're a supporter of the
Dogooder party and you know that nearly everyone aligns with you. You never have
anything to gain by bringing politics into work. ∎&lt;/p&gt;
&lt;h3 id="scenario-2-the-project-predicament"&gt;Scenario 2. The Project Predicament&lt;/h3&gt;
&lt;p&gt;You're in a meeting, and you spot a flaw in the project plan. Do you speak up?&lt;/p&gt;
&lt;p&gt;Let's play it out:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you speak up and you're right: Moderate gain (project improves, you look
smart)&lt;/li&gt;
&lt;li&gt;If you speak up and you're wrong: Small loss (mild embarrassment)&lt;/li&gt;
&lt;li&gt;If you stay quiet and the flaw is real: Large loss (project fails, you knew
and said nothing)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In this case, the potential gain outweighs the risk. Speak up, but do it thoughtfully.&lt;/p&gt;
&lt;h3 id="scenario-3-the-gossip-game"&gt;Scenario 3. The Gossip Game&lt;/h3&gt;
&lt;p&gt;Your colleague starts dishing out juicy gossip about the boss. Do you join in?&lt;/p&gt;
&lt;p&gt;Game theory says:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you join and it stays secret: Tiny gain (fleeting social connection)&lt;/li&gt;
&lt;li&gt;If you join and it gets back to the boss: Massive loss (lost trust, potential
career damage)&lt;/li&gt;
&lt;li&gt;If you stay quiet: No loss, potential gain (being seen as trustworthy)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Again, the smart move is to keep your mouth shut. The potential losses far
outweigh any possible gains.&lt;/p&gt;
&lt;h2 id="the-meta-game-building-your-reputation"&gt;The Meta-Game: Building Your Reputation&lt;/h2&gt;
&lt;p&gt;Here's where it gets interesting. Every time you play one of these "games" at
work, you're also playing a larger meta-game: building your reputation.&lt;/p&gt;
&lt;p&gt;Each time you choose to speak up or stay quiet, you're sending signals about who
you are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The person who always has something to say&lt;/li&gt;
&lt;li&gt;The quiet observer who speaks only when it truly matters&lt;/li&gt;
&lt;li&gt;The gossip&lt;/li&gt;
&lt;li&gt;The level-headed professional&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Your reputation is the sum of all these little games you play every day. And
unlike a single conversation, your reputation has long-lasting effects on your
career.&lt;/p&gt;
&lt;h2 id="implementing-game-theory-at-work"&gt;Implementing Game Theory at Work&lt;/h2&gt;
&lt;p&gt;So, how do you put this into practice?&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Pause before you speak&lt;/strong&gt;: Take a moment to consider the potential outcomes
of your words. What's the best-case scenario? The worst-case? Is it worth the
risk?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consider the long game&lt;/strong&gt;: How will your words affect your reputation over
time? Are you building the image you want? What do you have to gain or lose?
What does everyone else have to gain or lose?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observe the winners&lt;/strong&gt;: Look at the most respected people in your workplace.
When do they choose to speak up, and when do they stay quiet?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Practice strategic silence&lt;/strong&gt;: Sometimes, the most powerful move is to say
nothing at all. Let others fill the silence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;When you do speak, make it count&lt;/strong&gt;: Choose your moments carefully, and when
you do speak, ensure it adds real value.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Remember, nobody tells you exactly what to do at work. But with a little game
theory, you can figure out when to make your move and when to hold your cards
close to your chest.&lt;/p&gt;
&lt;p&gt;Now, if you'll excuse me, I'm going to shut up and get back to work. After all,
that's what the game theory matrix tells me to do right now.&lt;/p&gt;</description><author>swaits.com</author><pubDate>Tue, 03 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://swaits.com/game-theory-at-work-and-when-to-shutup/</guid></item><item><title>Does Chinese New Year impact the Chinese money supply (M0)?</title><link>https://bobbiechen.com/blog/2024/9/2/does-chinese-new-year-impact-the-chinese-money-supply-m0</link><description>&lt;p&gt;&lt;em&gt;Speculation ahead: I do not have any background in economics.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In China every year, the supply of cash in circulation (M0) shoots up by ~10% for one month, then drops back down. This seems to be because of Chinese New Year:&lt;/p&gt;












































  

    
  
    

      

      
        &lt;figure class="
              sqs-block-image-figure
              intrinsic
            "&gt;
          
        
        

        
          
            
          
            
                
                
                
                
                
                
                
                &lt;img alt="" height="820" src="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/c3d2c0d3-5f30-4c76-890a-43ddcf21705c/CN_Money_Supply_M0.png?format=1000w" width="1200" /&gt;

            
          
        
          
        

        
      
        &lt;/figure&gt;
      

    
  


  


&lt;p&gt;Traditionally, "red envelope" cash gifts are given to children during Chinese New Year,
which would require withdrawing cash.
If you look at the interactive version of &lt;a href="https://tradingeconomics.com/china/money-supply-m00"&gt;this graph from Trading Economics&lt;/a&gt;,
you'll see that there is a spike of ¥1 trillion or more, near the start of each calendar year.&lt;/p&gt;
&lt;p&gt;Why does the month change - sometimes January, sometimes February, or split between the two?
It matches the date of Chinese New Year that year, which depends on the lunar calendar.
After the holiday, the money is spent and deposited, causing the M0 supply to decrease.&lt;/p&gt;
&lt;p&gt;If this is true, you would expect to see similar patterns in other countries
that celebrate annual holidays with a tradition of cash gifts.
You can see similar, smaller blips in the M0 graphs of countries like
&lt;a href="https://tradingeconomics.com/singapore/money-supply-m0"&gt;Singapore&lt;/a&gt; (which has a large ethnic Chinese population), and
&lt;a href="https://tradingeconomics.com/south-korea/money-supply-m0"&gt;South Korea&lt;/a&gt; (with two blips per year: Seollal in the beginning of year and Chuseok in the fall).
But no such annual pattern is visible in the M0 graphs of areas like the &lt;a href="https://tradingeconomics.com/united-states/money-supply-m0"&gt;United States&lt;/a&gt;,
the &lt;a href="https://tradingeconomics.com/euro-area/money-supply-m0"&gt;Eurozone&lt;/a&gt;,
&lt;a href="https://tradingeconomics.com/brazil/money-supply-m0"&gt;Brazil&lt;/a&gt;,
or &lt;a href="https://tradingeconomics.com/saudi-arabia/money-supply-m0"&gt;Saudi Arabia&lt;/a&gt;.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;This post originates from a &lt;a href="https://news.ycombinator.com/item?id=40673619"&gt;Hacker News comment section&lt;/a&gt; where user &lt;em&gt;queuebert&lt;/em&gt; asked about the pattern in spikes.
As I mentioned at the top, I have no background in economics and I could be misinterpreting these numbers;
I couldn't find any (English-language) sources confirming or denying this Chinese New Year idea,
which is why I wrote this.
Any ideas?&lt;/p&gt;
&lt;p&gt;One more thing to think about: in recent years it seems the M0 cash spike has been shrinking.
This could be related to an increase in &lt;a href="https://www.technologyreview.com/2024/02/14/1088182/new-year-red-packet-apps/"&gt;digital red envelopes&lt;/a&gt; from apps like WeChat and AliPay, and &lt;a href="https://www.straitstimes.com/singapore/more-e-hongbaos-given-out-in-singapore-this-chinese-new-year"&gt;banking services&lt;/a&gt;.
Digital (and mobile) payments seem to be the norm in China these days;
when I visited last year, I bought a snack and paid with bills.
The store employee remarked, "Wow, it's rare for anyone to pay with cash these days."&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Edit: my friend Jean, who does live and work in China, suggests that this phenomenon may be related to end-of-year settlement business practices, where debts are paid at the end of the year (in cash or cash equivalents) rather than with Net-15 or Net-30 terms. I didn't investigate deeply, but let me know if you learn anything more. &lt;/p&gt;</description><author>Blog - Bobbie Chen</author><pubDate>Tue, 03 Sep 2024 01:03:29 GMT</pubDate><guid isPermaLink="true">https://bobbiechen.com/blog/2024/9/2/does-chinese-new-year-impact-the-chinese-money-supply-m0</guid></item><item><title>Deploy Container to Azure App Services with System-Assigned Identity</title><link>https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/</link><description>&lt;p&gt;In this blog post, I will guide you through deploying a custom container image to Azure App Services from a private container registry using a system-assigned managed identity and the Azure CLI. We will focus on leveraging the system-assigned managed identity approach in this article. For those interested in using a user-assigned managed identity, a [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/"&gt;Deploy Container to Azure App Services with System-Assigned Identity&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Mon, 02 Sep 2024 20:59:58 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-a-system-assigned-identity/</guid></item><item><title>2024-09-02-001</title><link>https://srijan.ch/notes/2024-09-02-001</link><description>Something precious stolen by magic. #WitchHatAtelier #Manga #Magic Syndicated to: https://bsky.app/profile/srijan4.bsky.social/post/3l36vuzxpbp24</description><author>Srijan Choudhary, all posts</author><pubDate>Mon, 02 Sep 2024 18:15:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2024-09-02-001</guid></item><item><title>The Power of Farm Animal Sanctuaries</title><link>https://joshbaldwin.substack.com/p/the-power-of-farm-animal-sanctuaries</link><description>How sanctuaries can be more impactful.</description><author>Josh Baldwin</author><pubDate>Mon, 02 Sep 2024 15:22:47 GMT</pubDate><guid isPermaLink="true">https://joshbaldwin.substack.com/p/the-power-of-farm-animal-sanctuaries</guid></item><item><title>30</title><link>https://utf9k.net/blog/30/</link><description>Reflections on turning 30</description><author>utf9k</author><pubDate>Mon, 02 Sep 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://utf9k.net/blog/30/</guid></item><item><title>Relive the Apollo 11 lunar landing</title><link>https://ilearnt.com/blog/lunarlanding/</link><description>&lt;p&gt;I was not old enough to remember the first man on the moon - in fact I wasn&amp;rsquo;t even born until the following year.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Mon, 02 Sep 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/lunarlanding/</guid></item><item><title/><link/><description>For all my new followers: don't be afraid to leave a reply or send a message. This is the internet and we don't necessarily need big tech's platforms as a middle man. The internet is already social media.</description><author>My First Timeline</author><pubDate>Mon, 02 Sep 2024 11:07:07 GMT</pubDate><guid isPermaLink="true"/></item><item><title>How to copy media off of an iPhone the hard way (using Linux)</title><link>https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;I helped a family member upgrade to a newer iPhone and make some room so that the internal storage does not run out.&lt;/p&gt;
&lt;p&gt;They had &lt;a href="https://nextcloud.com/"&gt;Nextcloud&lt;/a&gt; installed on the current phone, but due to limitations of the Nextcloud iOS
app, the backups only take place if the app itself is open, meaning that we had hundreds of photos and videos that were
not yet backed up.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;The network was slow and I was in a time crunch, so I opted to copy the media off of the phone by connecting the iPhone
to my laptop over
a Lightning cable. After entering the PIN on the phone again and approving the connection, I could see the iPhone
in my file manager, but opening it would show nothing at all. This was very unusual as other iPhones I used in the past
just worked on my laptop.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/happypath.png"&gt;
        &lt;img alt="This is what you'd expect to see when connecting an iPhone to a PC." height="378" src="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/happypath_hu_ad08bc532a2aef0e.png" style="width: auto; height: auto; border-radius: 8px;" width="482" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      This is what you'd expect to see when connecting an iPhone to a PC.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Rebooting the phone and connecting it again did nothing. Different known good Lightning cables did not improve things,
either.
Even a separate Windows 10 machine could not see any images, so it wasn&amp;rsquo;t a Linux thing.&lt;/p&gt;
&lt;p&gt;Various Apple-related posts online were devoid of useful advice.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;I was not ready to give up just yet and I finally stumbled
upon &lt;a href="https://askubuntu.com/a/1159559"&gt;this Ask Ubuntu answer&lt;/a&gt;.
This had potential, so I gave it a go, and I&amp;rsquo;m happy to report that it worked on my Fedora Linux machine!&lt;/p&gt;
&lt;p&gt;These are the steps that I took. The commands need to be run in a terminal window.&lt;/p&gt;
&lt;p&gt;Tested with Fedora Linux 40.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;install the necessary packages
&lt;ul&gt;
&lt;li&gt;on Fedora, run &lt;code&gt;sudo dnf install -y ifuse libimobiledevice libimobiledevice-utils&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;connect your iPhone to your PC
&lt;ul&gt;
&lt;li&gt;if your phone is not yet paired, then run &lt;code&gt;idevicepair pair&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;if it&amp;rsquo;s already paired, verify the connection by running &lt;code&gt;idevicepair validate&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;mount your phone to an empty folder of your choice
&lt;ul&gt;
&lt;li&gt;if an empty folder does not exist, run &lt;code&gt;mkdir ~/my-iphone&lt;/code&gt; (use any folder name that you want)&lt;/li&gt;
&lt;li&gt;to mount the phone, run &lt;code&gt;ifuse ~/my-iphone&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;you can now browse your media via your file manager or terminal by navigating to &lt;code&gt;~/my-iphone&lt;/code&gt;
&lt;ul&gt;
&lt;li&gt;photos and videos taken by the camera will be in the &lt;code&gt;DCIM&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;other folders, such as &lt;code&gt;Photos&lt;/code&gt; and &lt;code&gt;Downloads&lt;/code&gt; may also interest you&lt;/li&gt;
&lt;li&gt;to make a full copy of your phone to a folder named &lt;code&gt;iphone-backup&lt;/code&gt;, run &lt;code&gt;cp -r ~/my-iphone ~/iphone-backup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;alternative way to create a full backup: &lt;code&gt;rsync -aAXv ~/my-iphone ~/iphone-backup&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;once you&amp;rsquo;re done, unmount the iPhone by running &lt;code&gt;umount ~/my-iphone&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;it is now safe to disconnect your iPhone from the PC&lt;/li&gt;
&lt;/ul&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/iphone-contents.png"&gt;
        &lt;img alt="The directory listing of an iPhone." height="515" src="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/iphone-contents_hu_a92ef59c36fa4563.png" style="width: auto; height: auto; border-radius: 8px;" width="556" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      The directory listing of an iPhone.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/iphone-photos.png"&gt;
        &lt;img alt="Photos are located under DCIM folder, nested into a bunch of subfolders." height="515" src="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/iphone-photos_hu_542013aeebbdb8c2.png" style="width: auto; height: auto; border-radius: 8px;" width="556" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Photos are located under DCIM folder, nested into a bunch of subfolders.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/iphone-transfer.png"&gt;
        &lt;img alt="Ah yes, USB 2.0 speeds." height="252" src="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/iphone-transfer_hu_7ecfedb2879c7d23.png" style="width: auto; height: auto; border-radius: 8px;" width="593" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Ah yes, USB 2.0 speeds.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;If you have copied everything over and want to make more space on your phone using this method, then you need to delete
files under
the &lt;code&gt;DCIM&lt;/code&gt; folder &lt;em&gt;&lt;strong&gt;and&lt;/strong&gt;&lt;/em&gt; the cached thumbnails under the &lt;code&gt;PhotoData&lt;/code&gt; folder. iOS seems to generate thumbnails for
the photos you take and even if you delete all the actual photos off of the device, then the photo thumbnails will still
show up in the Photos app and elsewhere. This confused the hell out of me after I deleted the backed up photos off the phone the first time.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/iphone-thumbnails.png"&gt;
        &lt;img alt="Thumbnails are in a separate folder." height="235" src="https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/media/iphone-thumbnails_hu_493c2f083b992543.png" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Thumbnails are in a separate folder.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Alternatively, you can just delete files from the Photos app on the phone itself to avoid this nuance.&lt;/p&gt;
&lt;p&gt;Linux has its flaws, but at least it provides you with the power and tools needed to get you out of tricky situations.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;I can&amp;rsquo;t wait for &lt;a href="https://immich.app/"&gt;Immich&lt;/a&gt; to get a stable release&amp;hellip;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Apple and Microsoft related forums are &lt;em&gt;the worst&lt;/em&gt; for getting an actual solution to your problem, which is in stark
contrast to FOSS/Linux related discussions.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>./techtipsy</author><pubDate>Mon, 02 Sep 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/09/02/iphone-media-recovery/</guid></item><item><title>On this day, September  2</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-2/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2008 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Mon, 02 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-2/</guid></item><item><title>Reasons to write design docs</title><link>https://ntietz.com/blog/reasons-to-write-design-docs/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Sometimes I joke that as a principal engineer, my main programming language is English.
It's half true, though, since my job is as much about people and communication as it is about technology.
Probably more, actually.&lt;/p&gt;
&lt;p&gt;Writing is useful at all levels of software engineering.
It's not just something for tech leads, architects, and principal engineers.
We write all the time, whether it's comments in code, descriptions in Jira, messages in Slack, or design documents in a wiki.
We don't do this because it's fun; most engineers I've met don't &lt;em&gt;love&lt;/em&gt; writing&lt;sup class="footnote-reference" id="fr-love-writing-1"&gt;&lt;a href="https://ntietz.com/blog/reasons-to-write-design-docs/#fn-love-writing"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
We do it because it's useful.&lt;/p&gt;
&lt;p&gt;I've generally run into four main ways that writing design docs ends up being useful for me and the teams I'm on.
There may be more, and there are also ways they're &lt;em&gt;not&lt;/em&gt; useful.
Here they are with pithy summaries of how they're useful or not, with links to the full sections.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ntietz.com/blog/reasons-to-write-design-docs/#think-better"&gt;Writing a design doc helps you think, leading to better designs.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ntietz.com/blog/reasons-to-write-design-docs/#collab-better"&gt;Collaborating on a design doc with teammates improves the design.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ntietz.com/blog/reasons-to-write-design-docs/#share-better"&gt;Sharing the design doc with teammates broadens the organization's understanding of the design.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ntietz.com/blog/reasons-to-write-design-docs/#remember-better"&gt;Referring back to the design doc tells you why a decision was made.&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ntietz.com/blog/reasons-to-write-design-docs/#understand-unhelpful"&gt;Reading a design doc &lt;em&gt;will not&lt;/em&gt; tell you how the system works!&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's see how these shake out!
If you have any others, I'd love to hear them!&lt;/p&gt;
&lt;h1 id="think-better"&gt;Writing design docs helps you think&lt;/h1&gt;
&lt;p&gt;A popular conception of a really good engineer is that if you tell them a problem, they'll quickly tell you a solution.
With software teams, we sort of expect to tell them a problem and have them go heads down on the keyboard cranking out code.
Hands on keyboards, folks!&lt;/p&gt;
&lt;p&gt;That's not how solving problems really works, though.
For many things, I can probably give you &lt;em&gt;a&lt;/em&gt; solution quickly.
But it might be fatally flawed, and it certainly won't be optimal.
There wasn't time to think through all the details!&lt;/p&gt;
&lt;p&gt;This is where writing a design doc really helps with design.
There's a lot written about other techniques for thought, like going for walks and writing by hand.
I highly recommend these and they're where I get most of my best ideas for how to solve problems.
But writing a design doc isn't usually about generating the &lt;em&gt;ideas&lt;/em&gt;.
It's about expanding them and checking them and being thorough, and finding where your gaps are so you can solve the problems you didn't see yet.&lt;/p&gt;
&lt;p&gt;Putting a design into words and diagrams means that you have to make the design more concrete.
Instead of handwaving about it, it goes down onto the page.
You can start to see the complexity of the system, so you can start thinking about how to chop out parts of that complexity.
Most of all, it lets you see things that just plain don't make sense.
Countless times, I've run into things that made sense in my head but as I type it out, I just &lt;em&gt;know&lt;/em&gt; it can't possibly work.
It's much better to find that out before you try to implement it!&lt;/p&gt;
&lt;h1 id="collab-better"&gt;Collaborating on a design doc improves the design&lt;/h1&gt;
&lt;p&gt;Writing a design doc by yourself is useful, and I use them for a lot of solo projects.
But they're &lt;em&gt;much&lt;/em&gt; better with other people to collaborate with.&lt;/p&gt;
&lt;p&gt;By yourself, you have blind spots.
Have you ever written a sentence where you you had a word repeated twice in a row&lt;sup class="footnote-reference" id="fr-repetition-1"&gt;&lt;a href="https://ntietz.com/blog/reasons-to-write-design-docs/#fn-repetition"&gt;[2]&lt;/a&gt;&lt;/sup&gt;, then read past it multiple times while editing?
It's amazing what our brains fail to see.
There are some techniques to notice those repeated words in writing, like reading it aloud, but little beats having someone else proofread it.&lt;/p&gt;
&lt;p&gt;When someone else reads your design doc, you get similar benefits.
They come into it with fresh eyes.
They'll find those double words, and they'll also spot areas where you've missed the mark on your design.
Any time a reader has a question about the doc, it's a signal that the document is unclear, and you should edit or rewrite part of it.&lt;/p&gt;
&lt;p&gt;It's pretty easy to collaborate on these documents in a work setting.
It is harder to get reviews for design documents for personal projects, but it is possible!
For this, I like to have friends read over the design and give me feedback, and I return the favor for them.&lt;/p&gt;
&lt;p&gt;It's clear to me that &lt;em&gt;writing&lt;/em&gt; a design doc is useful in itself, and I would keep doing it even if I just burned the document immediately after writing it.
The process of writing helps us!
But the benefits go so much further than that in an organization.&lt;/p&gt;
&lt;h1 id="share-better"&gt;Sharing design docs broadens the understanding of the design&lt;/h1&gt;
&lt;p&gt;Imagine a software engineering org where every team makes its design decisions by talking out loud and scribbling on the whiteboard, then jumps to code without a design doc.
You might work at one right now!
How do you find out what other teams are working on?&lt;/p&gt;
&lt;p&gt;In orgs like this one, a lot of knowledge and news is just passed by word of mouth. You get coffee with your friend from another team, and she tells you that they're using a new database. Coffee with another friend, and he tells you that they've created a new kind of user account. These would have been nice to know for your team! And then you start wondering about why we use the &lt;em&gt;current&lt;/em&gt; database, so you ask your tech lead when you return from coffee, and they tell you what their previous tech lead told &lt;em&gt;them&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Poems and songs used to be passed down by oral tradition.
Many still are, but many have also been lost to time because they were never written down, and others have evolved in unknowable ways over the eons.
When we don't have design docs, then our understanding of the design is itself an oral tradition.
We learn it by passing around news and lore.
As people come and go from the company, this understanding may be corrupted or may vanish entirely.&lt;/p&gt;
&lt;p&gt;When we share design docs after writing them, we reduce these issues.
Now it's easier to see what changes other teams are working on: just read their design docs.
Since these docs are shared, everyone can get a common understanding of what changes are happening, and you get better organizational knowledge.&lt;/p&gt;
&lt;p&gt;They also help you understand &lt;em&gt;why&lt;/em&gt; a previous decision was made.&lt;/p&gt;
&lt;h1 id="remember-better"&gt;Referring to old design docs tells you why a decision was made&lt;/h1&gt;
&lt;p&gt;There is a famous story about a fence, told by one &lt;a href="https://en.wikipedia.org/wiki/Wikipedia:Chesterton%27s_fence"&gt;Chesterton&lt;/a&gt;.
Someone wanted to remove a fence, and they weren't allowed to until they could figure out the reason it was put there in the first place.
You don't typically build a fence for no reason, so don't remove it if you don't know why it's there.
This comes up a lot in software engineering, because we've all seen seemingly unnecessary bits that end up being load bearing fixes for critical bugs or edge cases.&lt;/p&gt;
&lt;p&gt;Without design docs, you have to try to piece together an understanding of why something is the way it is.
In the best case scenario, you can ask a coworker.
As an early employee at multiple companies, I've served in this role, which is also why I like design docs—I shouldn't be a single point of failure and my knowledge shouldn't all leave with me!
If you don't have anyone to ask, you can scrounge through the code for clues and look at the commit history.
However, commit history often gives you an incomplete picture of the "why" behind a change.&lt;/p&gt;
&lt;p&gt;It's much better to refer back to the original design doc associated with a change, if there is one.
Then you can see in the author's own words what changes they were intending to make and &lt;em&gt;why&lt;/em&gt; they wanted to do that.
In some cases, even the initial implementation and design doc drift apart, and they certainly will after much time has passed.
Regardless, the &lt;em&gt;intention and reasoning&lt;/em&gt; let you see what problem was being solved.
With that knowledge in hand, you can be more confident with your own changes.&lt;/p&gt;
&lt;p&gt;To make those changes, though, you still have to understand the system in its current state.&lt;/p&gt;
&lt;h1 id="understand-unhelpful"&gt;Reading a design doc will not tell you how the system works now&lt;/h1&gt;
&lt;p&gt;Unfortunately, design docs cannot tell you how a system works right now.
At best, they're an approximation of how it worked at one point in time.
Even if they're written right now, their correctness relies on one person or a small group of people understanding how the system works.
This understanding often has so many holes that it looks like swiss cheese!&lt;/p&gt;
&lt;p&gt;Design docs sit as snapshots of changes or of an overall architecture.
The doc tells you what the intention and problem were, but not even if it got implemented.
Some teams strive to update these docs, but that relies on human discipline to do so.
I mean... have you &lt;em&gt;met&lt;/em&gt; humans?
We're pretty bad at that followup thing, so relying on updates is fraught.&lt;/p&gt;
&lt;p&gt;They're even worse for overall system architecture.
They can give you a view of how someone &lt;em&gt;thinks&lt;/em&gt; the system works, but they won't tell you how the system &lt;em&gt;actually&lt;/em&gt; works.
For a sufficiently large software system (almost all of it), it's too big for any one person to fit in their head.
We can't fit the whole design with full correctness and all details into our heads.&lt;/p&gt;
&lt;p&gt;It's not all worthless, though, because even that approximation gives you a &lt;em&gt;starting point&lt;/em&gt;.
It tells you what other people understood about this system and lets you get started.
You can go from there to look at the code and see what was actually implemented or how things work now.
You start with something to anchor from instead of a complete blank slate.&lt;/p&gt;
&lt;h1 id="you-should-probably-write-more"&gt;You should probably write more&lt;/h1&gt;
&lt;p&gt;Design docs are one form of writing that is pretty essential for software engineering teams.
Without them, you're just not going to make good decisions, and you'll end up slower in the long run.
Bad decisions compound and slow you down.&lt;/p&gt;
&lt;p&gt;They're just one form of writing that helps us, though.
There are many others.
Writing can feel unproductive, because it's not &lt;em&gt;code&lt;/em&gt;, but it's essential.&lt;/p&gt;
&lt;p&gt;The beauty of writing is that it is communication that lasts.
We invented writing for a reason, instead of persisting with only oral traditions.
When you write something down, more people can read it and benefit from it for longer.&lt;/p&gt;
&lt;p&gt;Most teams I've seen don't have enough writing in place.
I totally get it, because I have the same instincts and fall into the same traps, and &lt;em&gt;my&lt;/em&gt; starting point is that I &lt;em&gt;love&lt;/em&gt; to write.
Even with that, I will routinely start on things for my own projects (and even at work, ssshhhh) without a solid design doc.
This is a mistake, and we should write more of these!&lt;/p&gt;
&lt;p&gt;But not just design docs, we should write more in general.
Communication is key to, well, everything in life?
Writing is a fantastic way to communicate.
If you write some fiction, an essay, or a poem, each of these will ultimately improve your communication.
And then hey, maybe you've used your love of productivity to hack your brain into letting you do something fun for yourself.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Thank you to &lt;a href="https://erikarow.land/"&gt;Erika Rowland&lt;/a&gt; and &lt;a href="https://sokolskayatranslations.com/"&gt;Eugenia Tietz-Sokolskaya&lt;/a&gt; for feedback on a draft of this post.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-love-writing"&gt;
&lt;p&gt;I do love to write, and one of the projects I did at my internship started with writing a report about different options.
Writing has fueled a lot of my career, and I hope to inspire and help others write! &lt;a href="https://ntietz.com/blog/reasons-to-write-design-docs/#fr-love-writing-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-repetition"&gt;
&lt;p&gt;I introduced one of these intentionally in this article, then missed it when copying it from my notes into this post. &lt;a href="https://ntietz.com/blog/reasons-to-write-design-docs/#fr-repetition-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 02 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/reasons-to-write-design-docs/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Building Resilience with kube-probesim</title><link>https://www.hoelzel.it/kubernetes/2024/09/02/kube-probesim.html</link><description>As the creator of kube-probesim, I wanted to solve a specific problem: simulating how Kubernetes applications handle liveness and readiness probe failures. This tool was born out of a need to quickly replicate real-world failure conditions like random probe failures, latency spikes, and failing external dependencies in a controlled manner.</description><author>{ Hoelzel.IT }</author><pubDate>Mon, 02 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.hoelzel.it/kubernetes/2024/09/02/kube-probesim.html</guid></item><item><title>2024-09-01-002</title><link>https://srijan.ch/notes/2024-09-01-002</link><description>My small #emacs #orgmode #gtd customization of the day: org-edna is a plugin that can be used to setup auto triggers (and blockers) when completing a task. org-gtd uses it to auto-forward the next TODO item in a project to NEXT when a task in the project is marked as DONE. The #orgedna trigger it uses is: relatives(forward-no-wrap todo-only 1 no-sort) todo!(NEXT). This works okay for me, but also …</description><author>Srijan Choudhary, all posts</author><pubDate>Mon, 02 Sep 2024 00:45:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2024-09-01-002</guid></item><item><title>Unpublished Project Backlog</title><link>https://zef.studio/projects/backlog</link><description>A bunch of projects that I'd like to post about separately.</description><author>Zef Houssney</author><pubDate>Sun, 01 Sep 2024 21:00:00 GMT</pubDate><guid isPermaLink="true">https://zef.studio/projects/backlog</guid></item><item><title>Scrolling to Soaring: Unleash Your Potential with Intentional Living</title><link>https://ananthmajumdar.substack.com/p/scrolling-to-soaring-unleash-your</link><description>How to Beat Distractions and Get Things Done</description><author>Ananth's Reflections</author><pubDate>Sun, 01 Sep 2024 18:26:55 GMT</pubDate><guid isPermaLink="true">https://ananthmajumdar.substack.com/p/scrolling-to-soaring-unleash-your</guid></item><item><title>React navigation basics</title><link>https://whackylabs.com/js/react/navigation/2024/09/01/react-navigation-basics/</link><description>&lt;p&gt;I just realized that I’ve never made the photos app with react.js. So let’s do that today.&lt;/p&gt;

&lt;p&gt;&lt;img alt="React vs React Native" src="https://i.imgflip.com/922suj.jpg" /&gt;&lt;/p&gt;

&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;
&lt;p&gt;I’m going to use vite to build the app with javascript.&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;npm create vite@latest .
npm install
npm run dev
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And we have our app up and running.&lt;/p&gt;

&lt;p&gt;&lt;img alt="setup" src="https://whackylabs.com/assets/react-navigation/01-setup.png" /&gt;&lt;/p&gt;

&lt;h3 id="fetch-data"&gt;Fetch data&lt;/h3&gt;
&lt;p&gt;To fetch and display data in react is a three step process. First we need &lt;code class="language-plaintext highlighter-rouge"&gt;useState&lt;/code&gt; to hold the data:&lt;/p&gt;

&lt;div class="language-js highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;photoList&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPhotoList&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we need a &lt;code class="language-plaintext highlighter-rouge"&gt;useEffect&lt;/code&gt; to fetch the data. One way is to use the promises:&lt;/p&gt;

&lt;div class="language-js highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://jsonplaceholder.typicode.com/photos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;setPhotoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another way is to create an &lt;code class="language-plaintext highlighter-rouge"&gt;async function&lt;/code&gt; and call it from &lt;code class="language-plaintext highlighter-rouge"&gt;useEffect&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-js highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://jsonplaceholder.typicode.com/photos&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;setPhotoList&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then draw the UI.&lt;/p&gt;

&lt;h3 id="drawing-content"&gt;Drawing content&lt;/h3&gt;
&lt;p&gt;To render the data we can simply use the html unordered list&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
    &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photoList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Loading ...&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photoList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="photo-list" src="https://whackylabs.com/assets/react-navigation/02-photo-list.png" /&gt;&lt;/p&gt;

&lt;p&gt;In the true spirit of react we can probably move the &lt;code class="language-plaintext highlighter-rouge"&gt;li&lt;/code&gt; out as a reusable component and call it &lt;code class="language-plaintext highlighter-rouge"&gt;PhotoTile&lt;/code&gt;:&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PhotoTile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"photoTile"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;and reduce our &lt;code class="language-plaintext highlighter-rouge"&gt;App.jsx&lt;/code&gt; to&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"photoList"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photoList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PhotoTile&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, to make the list 2 columns we can use grid, flexbox or one of the infinite other methods out there, but my favorite is to simply use the &lt;code class="language-plaintext highlighter-rouge"&gt;column-count&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"photoList"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; 
  ...
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;


&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;photoList&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;column&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nx"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="two-columns" src="https://whackylabs.com/assets/react-navigation/03-two-columns.png" /&gt;&lt;/p&gt;

&lt;h3 id="navigation"&gt;Navigation&lt;/h3&gt;
&lt;p&gt;And now the real deal. The &lt;code class="language-plaintext highlighter-rouge"&gt;react-router-dom&lt;/code&gt;. This changes everything. First we need to install the dependency obviously.&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;npm install react-router-dom
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then we need to update the &lt;code class="language-plaintext highlighter-rouge"&gt;main.jsx&lt;/code&gt; to use the &lt;code class="language-plaintext highlighter-rouge"&gt;router&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createBrowserRouter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="nx"&gt;createRoot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;root&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;RouterProvider&lt;/span&gt; &lt;span class="na"&gt;router&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;router&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;StrictMode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;To navigate to the details, we need to register the route with params:&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;router&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;createBrowserRouter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Home&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/details/:id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;element&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Details&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then in the &lt;code class="language-plaintext highlighter-rouge"&gt;Details&lt;/code&gt; we can get the param value with &lt;code class="language-plaintext highlighter-rouge"&gt;useParams()&lt;/code&gt; hook&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Details&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useParams&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setPhoto&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`https://jsonplaceholder.typicode.com/photos/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;setPhoto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Loading&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"photoDetails"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PhotoTile&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;p&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;ul&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="two-columns" src="https://whackylabs.com/assets/react-navigation/04-details.png" /&gt;&lt;/p&gt;

&lt;p&gt;And then to navigate between screens we need to make use of the &lt;code class="language-plaintext highlighter-rouge"&gt;Link&lt;/code&gt; component:&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;PhotoTile&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt; &lt;span class="na"&gt;className&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"photoTile"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt; &lt;span class="na"&gt;to&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;img&lt;/span&gt; &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;alt&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Link&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;li&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then construct the details path from &lt;code class="language-plaintext highlighter-rouge"&gt;Home&lt;/code&gt; screen like:&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PhotoTile&lt;/span&gt;
  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;thumbnailUrl&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;`/details/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And back to home from the &lt;code class="language-plaintext highlighter-rouge"&gt;Details&lt;/code&gt; screen:&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PhotoTile&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;photo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;And there we have the basics of a React app with navigation. The &lt;code class="language-plaintext highlighter-rouge"&gt;react-router&lt;/code&gt; is always evolving, there were some new changes in the &lt;code class="language-plaintext highlighter-rouge"&gt;v6&lt;/code&gt; which I tried to use. But looks good and works like charm.&lt;/p&gt;

&lt;p&gt;The link from this experiment is available at &lt;a href="https://github.com/chunkyguy/PhotoApp/tree/master/react"&gt;https://github.com/chunkyguy/PhotoApp/tree/master/react&lt;/a&gt;.&lt;/p&gt;

&lt;h3 id="references"&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://react.dev/"&gt;react.dev/&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://vitejs.dev/guide"&gt;vitejs.dev/guide&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=Ul3y1LXxzdU"&gt;Learn React Router v6 In 45 Minutes&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://reactrouter.com/"&gt;reactrouter.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Sun, 01 Sep 2024 16:37:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/js/react/navigation/2024/09/01/react-navigation-basics/</guid></item><item><title>Note 149</title><link>https://qubyte.codes/notes/1725177408421</link><description>&lt;p&gt;This sparrowhawk caught a sparrow right in front of my eyes. I managed to take a picture of it through the back door before it vanished again.&lt;/p&gt;

    &lt;img alt="A sparrowhawk standing on grass, with a sparrow under one foot." src="https://qubyte.codes/images/1725177270982.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Sun, 01 Sep 2024 10:56:48 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1725177408421</guid></item><item><title>The Virtues of Groceries</title><link>https://cmart.blog/virtues-of-groceries/</link><description>&lt;p&gt;Warnings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;This topic, personal eating habits, is tired. Anything I could say, a different nerd has said better.&lt;/li&gt;
&lt;li&gt;I currently eat meat but try to avoid eating mammals, which I learned today is called &lt;a href="https://english.stackexchange.com/questions/536069/single-word-for-someone-who-does-not-eat-mammals-meat"&gt;mafism&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;I&amp;rsquo;m a mid-30s knowledge worker who lives with a partner. We share some meals spontaneously but don&amp;rsquo;t plan around it, each keeping our own separate food. My experience won&amp;rsquo;t generalize if your situation is &lt;em&gt;very&lt;/em&gt; different.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Returning from a long and &lt;a href="https://en.wikipedia.org/wiki/Flammekueche"&gt;tasty&lt;/a&gt; vacation, my partner and I made an unusually serious commitment to each other: &lt;strong&gt;no restaurant, cafe, or take-out food for a whole month.&lt;/strong&gt; We allowed one narrow exception, non-caloric beverages (e.g. black tea or coffee) in a social setting. That aside, everything we ate had to come from home (and ultimately from a grocery store). For this impatient millenial, getting &lt;em&gt;everything&lt;/em&gt; I eat from a grocery store was a big change of routine.&lt;/p&gt;</description><author>cmart's blog</author><pubDate>Sun, 01 Sep 2024 08:30:00 GMT</pubDate><guid isPermaLink="true">https://cmart.blog/virtues-of-groceries/</guid></item><item><title>Reflections of working in Crypto</title><link>https://olshansky.info/posts/reflections-of-working-in-crypto/</link><description>Liquidity is key. Marketing is just memes. It's all a gamble. It's still early.</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 01 Sep 2024 04:17:18 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/reflections-of-working-in-crypto/</guid></item><item><title>On this day, September 1</title><link>https://stop.zona-m.net/2024/09/on-this-day-september-1/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 1995 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Sun, 01 Sep 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/09/on-this-day-september-1/</guid></item><item><title>Fall in Love with the Problem, Not the Solution</title><link>https://olshansky.info/book/fall_in_love_with_the_problem/</link><description>Olshansky's review of Fall in Love with the Problem, Not the Solution</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 01 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/book/fall_in_love_with_the_problem/</guid></item><item><title>Fall in Love with the Problem, Not the Solution</title><link>https://olshansky.info/book/fall_in_love_with_the_problem_not_the_solution/</link><description>Olshansky's review of Fall in Love with the Problem, Not the Solution by Uri Levine</description><author>🦉 olshansky 🦁</author><pubDate>Sun, 01 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/book/fall_in_love_with_the_problem_not_the_solution/</guid></item><item><title>Pacman Backup</title><link>https://cookie.engineer/weblog/articles/pacman-backup.html</link><description>Share and reuse pacman updates offline via USB drives or mesh networks to save network bandwidth.</description><author>Cookie Engineer's Web Log</author><pubDate>Sun, 01 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://cookie.engineer/weblog/articles/pacman-backup.html</guid></item><item><title>go_wait_for_k8s</title><link>https://www.hoelzel.it/kubernetes/2024/09/01/go-wait-for-k8s.html</link><description>In Kubernetes, ensuring that dependent services are ready before your application starts can be a critical task. For instance, your application might rely on a PostgreSQL database, and you need to make sure the database is fully initialized and ready to accept connections before the app itself starts. Handling this properly often requires custom scripts or tools to manage readiness checks, which can get complex and error-prone.</description><author>{ Hoelzel.IT }</author><pubDate>Sun, 01 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.hoelzel.it/kubernetes/2024/09/01/go-wait-for-k8s.html</guid></item><item><title>Kuberntes Access Proxies</title><link>https://www.hoelzel.it/kubernetes/2024/09/01/k8s-access-proxy.html</link><description>Kubernetes has revolutionized the way we manage and deploy applications by providing powerful tools for orchestrating containers at scale. However, as your Kubernetes environment grows, so does the complexity of managing access. Kubernetes’ native Role-Based Access Control (RBAC) is essential but insufficient on its own to address the challenges of scale, security, and compliance. This is where an access proxy, such as Teleport, becomes indispensable.</description><author>{ Hoelzel.IT }</author><pubDate>Sun, 01 Sep 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.hoelzel.it/kubernetes/2024/09/01/k8s-access-proxy.html</guid></item><item><title>September 2024 - Bookmarks</title><link>https://domenicoluciani.com/2024/09/01/bookmarks.html</link><description>Bookmarks for September 2024: 1 link - Go net/http.ServeMux and Trailing Slashes - Xe Iaso.</description><author>Domenico Luciani</author><pubDate>Sun, 01 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://domenicoluciani.com/2024/09/01/bookmarks.html</guid></item><item><title>Breaking Bell's Inequality with Monte Carlo Simulations in Python</title><link>https://bytepawn.com/quantum-entanglement-bell-inequality-python.html</link><description>&lt;p&gt;The article explains the Bell inequality using Monte Carlo simulations in Python, and shows how non-local action-at-a-distance can be used to break it with entangled qubits.&lt;br /&gt;&lt;br /&gt; &lt;img alt="John Stewart Bell" src="/images/bell-game.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 01 Sep 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/quantum-entanglement-bell-inequality-python.html</guid></item><item><title>2024.08.DisappearingMoment</title><link>https://newsletter.disappearingmoment.com/archive/202408disappearingmoment/</link><description>&lt;p&gt;&lt;a href="https://newsletter.disappearingmoment.com/archive/202202disappearingmoment/" rel="noopener noreferrer nofollow" target="_blank"&gt;I stopped following the news and renounced social media&lt;/a&gt; in 2012. &lt;a href="https://newsletter.disappearingmoment.com/archive/202305disappearingmoment/" rel="noopener noreferrer nofollow" target="_blank"&gt;I stopped following sports&lt;/a&gt; in 2016.&lt;/p&gt;
&lt;p&gt;Over the past couple of years, I let myself start following sports again. For the most part, I've followed running. I like following it enough to write about it. That includes my longest work in years, &lt;a href="https://disappearingmoment.com/sifan-hassan-is-fine-the-way-she-is/" rel="noopener noreferrer nofollow" target="_blank"&gt;an essay about Sifan Hassan&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I also tried to get back into baseball in a small way. I’ve never had the time or money to go to many live games. I don’t have any interest in television or streaming services. I used to follow baseball through radio broadcasts, writers, and podcasters. To catch up on baseball, I subscribed to Molly Knight and Joe Posnanski. Then Substack revealed its vileness. Knight and Posnanski haven’t left Substack, so I won’t follow or promote their work.&lt;/p&gt;
&lt;p&gt;In June, I got an email message from a library friend:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;I feel like you and Craig Calcaterra are of a similar mind (Craig left Substack when they backed white supremacy). I didn't see his essential newsletter on your list.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Calcaterra is a prolific writer, practical and personable. He’s helping me learn about the teams and players in Major League Baseball. After eight years away, I recognize only a few names. A lot of other things have changed. I’m starting to catch up.&lt;/p&gt;
&lt;p&gt;My pleasure in Calcaterra made me wonder about one of my favorite writers, Rany Jazayerli. I followed Jazayerli’s posts on Usenet in the mid-90s, then his work as a cofounder of &lt;em&gt;Baseball Prospectus&lt;/em&gt;. He kept writing after he left, both on his own and for the most prestigious sports publications. He hosted a great, short-lived podcast with Joe Sheehan, a &lt;em&gt;Prospectus&lt;/em&gt; cofounder.&lt;/p&gt;
&lt;p&gt;During that time, Jazayerli went from college to medical school. He moved to Chicago and started a dermatology practice. The Chicago Cubs offered him a dream job and he turned it down. He felt an obligation to his employees, patients, and family. For many years, his main focus, when he has time to write, is the Kansas City Royals, his favorite baseball team.&lt;/p&gt;
&lt;p&gt;I learned that Jazayerli hosts a podcast about the Royals. His co-host, Soren Petro, a radio announcer, serves as a surrogate for the fans. They started the show in April 2022. I like how they interact.&lt;/p&gt;
&lt;p&gt;The show checks my boxes. Listening to it is efficient: it’s released weekly and I like binging it, so I don’t feel compelled to keep up. It’s &lt;a href="https://en.wikipedia.org/wiki/Parasocial_interaction" rel="noopener noreferrer nofollow" target="_blank"&gt;parasocial&lt;/a&gt;: Rany is good at revealing the right amount of information about himself. It’s optimistic: Rany seems incapable of cynicism, pessimism, or cruelty. It is esoteric and nerdy. Very, very, very nerdy. Rany also does something better than most baseball pundits.&lt;/p&gt;
&lt;p&gt;Like other nerdy baseball fans of our generation, Rany and I grew up on Bill James. In a post-Bill James world. As geeks like us took over, baseball analytics and fantasy sports became pervasive. We identified with executives more than players. It was baseball’s equivalent of wrestling’s &lt;a href="https://en.wikipedia.org/wiki/Kayfabe" rel="noopener noreferrer nofollow" target="_blank"&gt;kayfabe&lt;/a&gt;. We were post-modern baseball fans. The teams were &lt;a href="https://en.wikipedia.org/wiki/Granfalloon" rel="noopener noreferrer nofollow" target="_blank"&gt;granfalloons&lt;/a&gt;. Only marks &lt;a href="https://vimeo.com/47283296" rel="noopener noreferrer nofollow" target="_blank"&gt;rooted for laundry&lt;/a&gt;. The off-season, drafts, and transactions were as exciting as the games.&lt;/p&gt;
&lt;p&gt;Rany understands that perspective. He honors it. And he also loves watching good players play well. He wants them to play for his beloved Royals. He wants the Royals to win. His desire to see the Royals win makes me want them to win. His joy brings me joy.&lt;/p&gt;
&lt;p&gt;Welcome to August 2024’s Disappearing Moment, an inventory of my experiences. I hope you enjoy it.&lt;/p&gt;
&lt;h2&gt;Podcasts&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://podcasts.apple.com/us/podcast/kauffman-corner/id1621122528" rel="noopener noreferrer nofollow" target="_blank"&gt;Kauffman Corner&lt;/a&gt; (I Loved It): Rany Jazayerli talking about the Royals. Few things make me happier.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://www.washingtonpost.com/podcasts/the-sports-moment/" rel="noopener noreferrer nofollow" target="_blank"&gt;The Sports Moment&lt;/a&gt; (I Liked It): I miss the Olympics. This was a fun way to keep up.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://www.iheart.com/podcast/1119-weird-little-guys-201395214/" rel="noopener noreferrer nofollow" target="_blank"&gt;Weird Little Guys&lt;/a&gt; (A Personal Favorite): White supremacists are dipshits: great premise. Molly Conger: great host. This show will get better as it finds its rhythm. Or a better network. &lt;em&gt;(Postscript: She found her rhythm. This was originally, “I Liked It”. It’s now, “A Personal Favorite”.)&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Nerdy Software&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://www.nal.gov.au/nal_products/nalscribe/" rel="noopener noreferrer nofollow" target="_blank"&gt;NALScribe&lt;/a&gt; works well for captioning live speech. Try it for communicating with people with hearing loss.&lt;/p&gt;
&lt;h2&gt;Free Font&lt;/h2&gt;
&lt;p&gt;Nothing is &lt;a href="https://authentic.website/sans.html" rel="noopener noreferrer nofollow" target="_blank"&gt;authentic&lt;/a&gt; except the gadfly who reveals its absence&lt;/p&gt;
&lt;h2&gt;Bougie Products&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://kensuifitness.com/products/swissies" rel="noopener noreferrer nofollow" target="_blank"&gt;Swissies&lt;/a&gt; are inexpensive, well designed, and useful. If you lift weights at home, they’re worth considering.&lt;/p&gt;
&lt;h2&gt;Personal Finance and Investing&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://npd.pentester.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;Did National Public Data abuse your privacy&lt;/a&gt;? If it did, &lt;a href="https://newsletter.disappearingmoment.com/archive/202206disappearingmoment/" rel="noopener noreferrer nofollow" target="_blank"&gt;freeze your credit&lt;/a&gt;. If it didn’t, don’t rely on luck. Freeze it now.&lt;/p&gt;
&lt;h2&gt;Reading&lt;/h2&gt;
&lt;ul&gt;&lt;li&gt;&lt;p&gt;Michael Buckland, “&lt;a href="https://journals.ala.org/index.php/lrts/article/view/8132/11520" rel="noopener noreferrer nofollow" target="_blank"&gt;Known Item Search and Subject Search&lt;/a&gt;” (I Loved It): &lt;a href="https://people.ischool.berkeley.edu/~buckland/bibliogr.pdf" rel="noopener noreferrer nofollow" target="_blank"&gt;Buckland’s scholarship&lt;/a&gt; starts where &lt;a href="https://www.researchgate.net/publication/32956275_A_Brilliant_Mind_Margaret_Egan_and_Social_Epistemology" rel="noopener noreferrer nofollow" target="_blank"&gt;Margaret Egan&lt;/a&gt; ended. Their ambition, rigor, and fluidity set the standard for my profession.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Carolina Carter, &lt;a href="https://carolinacarter.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;&lt;em&gt;After Him&lt;/em&gt;&lt;/a&gt; (2017) (I Loved It): We, as readers, are as malleable as the author’s characters. What else is there to know?&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Curtis Sittenfeld, "&lt;a href="https://www.nytimes.com/2024/08/20/opinion/beach-read-ai.html" rel="noopener noreferrer nofollow" target="_blank"&gt;An Experiment in Lust, Regret and Kissing&lt;/a&gt;" (I Loved It): Please may I live in a world where cheaters lose.&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Thomas J. H. Morgan and Paul E. Smaldino, “&lt;a href="https://osf.io/preprints/osf/3ez9v" rel="noopener noreferrer nofollow" target="_blank"&gt;Author-Paid Publication Fees Corrupt Science and Should Be Abandoned&lt;/a&gt;” (I Liked It): Fuck Elsevier. It has done incalculable damage, akin to Facebook and Fox. Green and Diamond Open Access is a necessary response.&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;h2&gt;Famous and Relatable&lt;/h2&gt;
&lt;p&gt;We have different definitions of fame. We empathize with different people. For me, the relevant factors are:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;p&gt;Authenticity&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Principles&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;A sense that people who know them like them&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Self-awareness&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Reluctance (to be famous)&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Awkwardness&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;These are some famous people with whom I find it easy to empathize:&lt;/p&gt;
&lt;ul&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://olympics.com/en/news/nia-akins-aims-for-800-metre-glory-paris-2024-olympics" rel="noopener noreferrer nofollow" target="_blank"&gt;Nia Akins&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Justin_Amash" rel="noopener noreferrer nofollow" target="_blank"&gt;Justin Amash&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://www.casadecalexico.com/about/" rel="noopener noreferrer nofollow" target="_blank"&gt;Joey Burns (and John Convertino)&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Pete_Buttigieg" rel="noopener noreferrer nofollow" target="_blank"&gt;Pete Buttigieg&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Michael_Cera" rel="noopener noreferrer nofollow" target="_blank"&gt;Michael Cera&lt;/a&gt; and &lt;a href="https://publicchoice.gmu.edu/tylercowen" rel="noopener noreferrer nofollow" target="_blank"&gt;Tyler Cowan&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://www.strava.com/pros/12019441" rel="noopener noreferrer nofollow" target="_blank"&gt;Keira D’Amato&lt;/a&gt; and &lt;a href="https://www.courtneydauwalter.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;Courtney Dauwalter&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Ben_Edlund" rel="noopener noreferrer nofollow" target="_blank"&gt;Ben Edlund&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Theo_Epstein" rel="noopener noreferrer nofollow" target="_blank"&gt;Theo Epstein&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://elenaferrante.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;Elena Ferrante&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Paul_Ford_%28technologist%29" rel="noopener noreferrer nofollow" target="_blank"&gt;Paul Ford&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://lawprose.org/bryan-garner/" rel="noopener noreferrer nofollow" target="_blank"&gt;Bryan Garner&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Maggie_Gyllenhaal" rel="noopener noreferrer nofollow" target="_blank"&gt;Maggie Gyllenhaal&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Ethan_Hawke" rel="noopener noreferrer nofollow" target="_blank"&gt;Ethan Hawke&lt;/a&gt; and &lt;a href="https://www.loc.gov/about/about-the-librarian/" rel="noopener noreferrer nofollow" target="_blank"&gt;Carla Hayden&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Miguel_de_Icaza" rel="noopener noreferrer nofollow" target="_blank"&gt;Miguel de Icaza&lt;/a&gt; and &lt;a href="https://www.nba.com/watch/video/allen-iverson-hall-of-fame-speech" rel="noopener noreferrer nofollow" target="_blank"&gt;Allen Iverson&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Jameela_Jamil" rel="noopener noreferrer nofollow" target="_blank"&gt;Jameela Jamil&lt;/a&gt; and &lt;a href="https://ysph.yale.edu/news-article/popular-epidemiologist-lays-out-future-path-of-public-health-communication/" rel="noopener noreferrer nofollow" target="_blank"&gt;Katelyn Jetelina&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Starlee_Kine" rel="noopener noreferrer nofollow" target="_blank"&gt;Starlee Kine&lt;/a&gt; and &lt;a href="https://www.mariakonnikova.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;Maria Konnikova&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://www.bloomberg.com/opinion/authors/ARbTQlRLRjE/matthew-s-levine" rel="noopener noreferrer nofollow" target="_blank"&gt;Matt Levine&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Desiree_Linden" rel="noopener noreferrer nofollow" target="_blank"&gt;Des Linden&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Frances_McDormand" rel="noopener noreferrer nofollow" target="_blank"&gt;Frances McDormand&lt;/a&gt; and &lt;a href="https://nicomuhly.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;Nico Muhly&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Steve_Nash" rel="noopener noreferrer nofollow" target="_blank"&gt;Steve Nash&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Yared_Nuguse" rel="noopener noreferrer nofollow" target="_blank"&gt;Yared Nuguse&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://www.whitehouse.gov/about-the-white-house/first-families/michelle-obama/" rel="noopener noreferrer nofollow" target="_blank"&gt;Michelle Obama&lt;/a&gt; and &lt;a href="https://www.ocasiocortez.com/about" rel="noopener noreferrer nofollow" target="_blank"&gt;Alexandria Ocasio-Cortez&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://dollyparton.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;Dolly Parton&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Mark_Pilgrim" rel="noopener noreferrer nofollow" target="_blank"&gt;Mark Pilgrim&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://questlove.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;Questlove&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Robin_Quivers" rel="noopener noreferrer nofollow" target="_blank"&gt;Robin Quivers&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/David_Rees_%28cartoonist%29" rel="noopener noreferrer nofollow" target="_blank"&gt;David Rees&lt;/a&gt; and &lt;a href="https://maryroach.net/" rel="noopener noreferrer nofollow" target="_blank"&gt;Mary Roach&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://counciloncj.org/ccj-directory/david-singleton/" rel="noopener noreferrer nofollow" target="_blank"&gt;David Singleton&lt;/a&gt; and &lt;a href="https://curtissittenfeld.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;Curtis Sittenfeld&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://rashidaforcongress.com/about/" rel="noopener noreferrer nofollow" target="_blank"&gt;Rashida Tlaib&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Christy_Turlington" rel="noopener noreferrer nofollow" target="_blank"&gt;Christy Turlington&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://tracey-ullman.blogspot.com/p/about_4.html" rel="noopener noreferrer nofollow" target="_blank"&gt;Tracey Ullman&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Gabrielle_Union" rel="noopener noreferrer nofollow" target="_blank"&gt;Gabrielle Union&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Gus_Van_Sant" rel="noopener noreferrer nofollow" target="_blank"&gt;Gus Van Sant&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Joey_Votto" rel="noopener noreferrer nofollow" target="_blank"&gt;Joey Votto&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Sam_Wang_%28neuroscientist%29" rel="noopener noreferrer nofollow" target="_blank"&gt;Sam Wang&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Meg_White" rel="noopener noreferrer nofollow" target="_blank"&gt;Meg White&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Gao_Xingjian" rel="noopener noreferrer nofollow" target="_blank"&gt;Gao Xingjian&lt;/a&gt; and &lt;a href="https://www.wendy-xu.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;Wendy Xu&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Kevin_Youkilis" rel="noopener noreferrer nofollow" target="_blank"&gt;Kevin Youkilis&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/David_Yow" rel="noopener noreferrer nofollow" target="_blank"&gt;David Yow&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;a href="https://www.president.gov.ua/en/president/biografiya" rel="noopener noreferrer nofollow" target="_blank"&gt;Volodymyr Zelenskyy&lt;/a&gt; and &lt;a href="https://vimeo.com/tonyzhou" rel="noopener noreferrer nofollow" target="_blank"&gt;Tony Zhou (and Taylor Ramos)&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;Thank you for spending a few moments with me. I appreciate you and look forward to corresponding again next month.&lt;/p&gt;
&lt;p&gt;Brett&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Want to discuss any of the topics in this newsletter or anything else with other Disappearing Moment readers? Please sign up for &lt;/em&gt;&lt;a href="https://august.disappearingmoment.com/" rel="noopener noreferrer nofollow" target="_blank"&gt;&lt;em&gt;Perpetual August&lt;/em&gt;&lt;/a&gt;&lt;em&gt;. I think it might be fun.&lt;/em&gt;&lt;/p&gt;</description><author>Disappearing Moment</author><pubDate>Sun, 01 Sep 2024 00:03:00 GMT</pubDate><guid isPermaLink="true">https://newsletter.disappearingmoment.com/archive/202408disappearingmoment/</guid></item><item><title>Two Rules of AI Business and Startups That Ignore Them</title><link>https://svedic.org/programming/two-rules-of-ai-business-and-startups-that-ignore-them</link><description>These rules are not new, and they are not mine; I stole them from Andrew Ng and Benedict Evans, two men with a huge following. Still, a large majority of AI entrepreneurs and engineers don’t pay attention to them, maybe &amp;#8230; &lt;a href="https://svedic.org/programming/two-rules-of-ai-business-and-startups-that-ignore-them"&gt;Continue reading &lt;span class="meta-nav"&gt;&amp;#8594;&lt;/span&gt;&lt;/a&gt;</description><author>Svedic.org</author><pubDate>Sat, 31 Aug 2024 16:23:15 GMT</pubDate><guid isPermaLink="true">https://svedic.org/programming/two-rules-of-ai-business-and-startups-that-ignore-them</guid></item><item><title>Debugging Wake on Lan</title><link>https://gaganpreet.in/posts/debugging-wake-on-lan/</link><description>&lt;p&gt;I rely heavily on Wake-on-LAN to wake my desktop and ssh in for working remotely. Last month I ran into an issue; Wake-on-LAN wasn&amp;rsquo;t working. I had to physically press the button or press a key on the keyboard to wake up my desktop.&lt;/p&gt;
&lt;p&gt;Over a weekend, I sat down to figure out what was going on.&lt;/p&gt;
&lt;h3 class="relative group" id="setting-the-wake-on-flag-manually"&gt;Setting the wake-on flag manually &lt;span class="absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100"&gt;&lt;a class="group-hover:text-primary-300 dark:group-hover:text-neutral-700" href="#setting-the-wake-on-flag-manually"&gt;#&lt;/a&gt;&lt;/span&gt;&lt;/h3&gt;&lt;p&gt;The &lt;a href="https://wiki.archlinux.org/title/Wake-on-LAN" rel="noreferrer" target="_blank"&gt;Arch Wiki Wake-on-LAN&lt;/a&gt; page has a bunch of suggestions on how to get Wake-on-LAN working correctly, but it wasn&amp;rsquo;t obvious to me what had changed in a system that had been working fine for over two years. Running &lt;code&gt;sudo ethtool enp34s0  | grep Wake-on:&lt;/code&gt; showed that &lt;code&gt;Wake-on&lt;/code&gt; value was &lt;code&gt;d&lt;/code&gt;, disabled.&lt;/p&gt;</description><author>Gaganpreet Arora</author><pubDate>Sat, 31 Aug 2024 10:23:34 GMT</pubDate><guid isPermaLink="true">https://gaganpreet.in/posts/debugging-wake-on-lan/</guid></item><item><title>Music updates (August 2024)</title><link>https://bobbiechen.com/blog/2024/8/31/music-updates-september-2024</link><description>&lt;p&gt;It's been a while since my last music update, though it's not because I haven't been doing musical things.&lt;/p&gt;
&lt;p&gt;Since &lt;a href="https://bobbiechen.com/blog/2023/11/29/music-updates-november-2023"&gt;my last update in November&lt;/a&gt;, I played with the SF Castaways at DumpsterFest 2023, Henry and Daniel's holiday housewarming, Gloria and Viraj's rooftop, Isabel and Patrick's garden, and Babette for Kim's retirement party; plus the Golden Gate Park Bandshell with Dumpster Love.&lt;/p&gt;




  

  



  
    
      

        
          
            
              
                &lt;img alt="dumpsterfest.JPG" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725081955478-526620MQDSRE4XHG90MI/dumpsterfest.JPG?format=1000w" /&gt;&lt;br /&gt;
              

              
              
            
          
          
        

        

        

      

        
          
            
              
                &lt;img alt="christmas.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725082311430-RKBWBTT5GKHTOK0RHI0O/christmas.jpg?format=1000w" /&gt;&lt;br /&gt;
              

              
              
            
          
          
        

        

        

      

        
          
            
              
                &lt;img alt="garden_party.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725082328703-6AN13BJNLNHX0A0JJH23/garden_party.jpg?format=1000w" /&gt;&lt;br /&gt;
              

              
              
            
          
          
        

        

        

      

        
          
            
              
                &lt;img alt="partiful-JazzSoiree3-23.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725082441840-KAIOFFMT17QXWNRQT8C6/partiful-JazzSoiree3-23.jpg?format=1000w" /&gt;&lt;br /&gt;
              

              
              
            
          
          
        

        

        

      

        
          
            
              
                &lt;img alt="bandshell.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725082581087-NL3OK2718J27CJ769NZC/bandshell.jpg?format=1000w" /&gt;&lt;br /&gt;
              

              
              
            
          
          
        

        

        

      
    
  

  




  

    
      
          

        

        
      
          

        

        
      
          

        

        
      
          

        

        
      
          

        

        
      
    

  





&lt;p&gt;I saw a bunch of live music from my friends
including Emily's band (rip),
Alex's band (didn't catch the name),
Brian's &lt;a href="https://shows.cool/" tabindex="0"&gt;shows.cool&lt;/a&gt; show,
&lt;a href="https://www.instagram.com/dumpster.love/" tabindex="0"&gt;Dumpster Love&lt;/a&gt; and the other DumpsterFest performers,
&lt;a href="https://www.afterglowchorus.com/" tabindex="0"&gt;Afterglow Chorus&lt;/a&gt;,
&lt;a href="https://www.instagram.com/stfvocals" tabindex="0"&gt;Short Term Fun&lt;/a&gt;,
and Keni (at her own wedding!).&lt;/p&gt;



  

  



  
    
      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                      js-gallery-lightbox-opener
                    
                    content-fit
                  " href="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725083540837-3PNDGHRPSQJ62VQG9K66/456048857_803644565308523_6228126097929075165_n.jpg"&gt;
                  
                    &lt;span class="v6-visually-hidden"&gt;View fullsize&lt;/span&gt;
                  
                  &lt;img alt="456048857_803644565308523_6228126097929075165_n.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725083540837-3PNDGHRPSQJ62VQG9K66/456048857_803644565308523_6228126097929075165_n.jpg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                      js-gallery-lightbox-opener
                    
                    content-fit
                  " href="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725083541943-BZUVFOJTDAJ4C72OHCQD/dumpsterlove.jpg"&gt;
                  
                    &lt;span class="v6-visually-hidden"&gt;View fullsize&lt;/span&gt;
                  
                  &lt;img alt="dumpsterlove.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725083541943-BZUVFOJTDAJ4C72OHCQD/dumpsterlove.jpg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      

        

        

        
          
            
              
                
                &lt;a class="
                    image-slide-anchor
                    
                      js-gallery-lightbox-opener
                    
                    content-fit
                  " href="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725083540984-R6Z100QI1N1OMDZCP5AY/456201402_1167520651025260_4181116665417318313_n.jpg"&gt;
                  
                    &lt;span class="v6-visually-hidden"&gt;View fullsize&lt;/span&gt;
                  
                  &lt;img alt="456201402_1167520651025260_4181116665417318313_n.jpg" class="thumb-image" src="https://images.squarespace-cdn.com/content/v1/595b129c72af65691b797b34/1725083540984-R6Z100QI1N1OMDZCP5AY/456201402_1167520651025260_4181116665417318313_n.jpg?format=1000w" /&gt;&lt;br /&gt;
                &lt;/a&gt;
                
              
            
          

          
        

      
    
  

  










&lt;p&gt;For the Castaways, Daniel moved to New York, ending an era for us
(remember, the original name was Daniel Li Quartet).
We're still playing, sans pianist for now.
Even though it was planned for a while,
I find myself thinking I should have cherished our time together earlier - as musicians, and as friends who got to see each other frequently.
The same goes for Joanne when she moved back to SoCal,
and Andy before we parted ways for college.&lt;/p&gt;
&lt;p&gt;On the other hand,
lately I've been feeling more like a part of a musical community here in SF.
I played saxophone and accordion for Dumpster Love recently -
a band I met through Ted, a friend who goes back to high school (middle school, if you're really counting).
At DumpsterFest, their private music festival featuring friends' bands, I ran into Grace again,
someone I had first met at a funk band jam session in Mission Dolores Park a few years ago (when JLi invited me).
Angel's new group mixes friends from Afterglow with the Castaways crew.
In the long run, all acts are fleeting anyways.
All the more reason to enjoy it.&lt;/p&gt;

&lt;hr /&gt;
&lt;p&gt;Here's a recommendations from the past several months:&lt;/p&gt;
&lt;h3&gt;Never Tell - Luke Chiang&lt;/h3&gt;


&lt;p&gt;&lt;a href="https://open.spotify.com/track/72Vu0dhVlpjtdPdli224Nf?si=8db5cfb3fac64ffb" tabindex="0"&gt;Spotify link&lt;/a&gt; | &lt;a href="https://www.instagram.com/lukaschiang" tabindex="0"&gt;Instagram&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Luke Chiang's comeback song is worth the listen, following a long hiatus due to a health issue that affected his singing.
His music is beautiful, and it's the rare treat to listen to such a deep voice.&lt;/p&gt;
&lt;h3&gt;ELOWI - Yamê&lt;/h3&gt;


&lt;p&gt;&lt;a href="https://open.spotify.com/album/49TUnWv5swURA9osAyLHI2?si=_umxY-ZoQziBSw9i8aF4Pw" tabindex="0"&gt;Spotify link&lt;/a&gt; | &lt;a href="https://www.instagram.com/yamebantu/?hl=en" tabindex="0"&gt;Instagram&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The first time I heard Yamê's song Bécane,
it was through &lt;a href="https://www.tiktok.com/@thekittyandrea?lang=en" tabindex="0"&gt;Andrea von Speed&lt;/a&gt;'s surrealist cat TikToks.
I was obsessed.&lt;/p&gt;
&lt;p&gt;I don't pretend to understand M'bo or French,
and I don't need to to enjoy this album.
It's a complete work that you can listen to end-to-end,
with luscious production and captivating singing+rapping.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;As always, if you'd like to do anything musical, reach out!
Until next time - hopefully in less than ten months.&lt;/p&gt;</description><author>Blog - Bobbie Chen</author><pubDate>Sat, 31 Aug 2024 08:55:44 GMT</pubDate><guid isPermaLink="true">https://bobbiechen.com/blog/2024/8/31/music-updates-september-2024</guid></item><item><title>Curing the Framework Curse</title><link>https://gavinhoward.com/2024/08/curing-the-framework-curse/</link><description>GUI frameworks have a curse, and I'm going to try to cure it.</description><author>Gavin D. Howard</author><pubDate>Sat, 31 Aug 2024 07:49:07 GMT</pubDate><guid isPermaLink="true">https://gavinhoward.com/2024/08/curing-the-framework-curse/</guid></item><item><title>On this day, August 31</title><link>https://stop.zona-m.net/2024/08/on-this-day-august-31/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 1941 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Sat, 31 Aug 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/08/on-this-day-august-31/</guid></item><item><title>Motor Physics</title><link>https://evjang.com/2024/08/31/motors.html</link><description>1X released a teaser video of NEO Beta to the world yesterday.</description><author>Eric Jang</author><pubDate>Sat, 31 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://evjang.com/2024/08/31/motors.html</guid></item><item><title>Streamlining Helm Chart Management with Argo Helm Versioner</title><link>https://www.hoelzel.it/devops/2024/08/31/argo-helm-versioner.html</link><description>For anyone managing Kubernetes environments with Argo CD, keeping Helm charts up-to-date is a routine but essential task. Argo Helm Versioner is a straightforward tool that helps automate this process, ensuring your deployments stay current without adding extra overhead.</description><author>{ Hoelzel.IT }</author><pubDate>Sat, 31 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.hoelzel.it/devops/2024/08/31/argo-helm-versioner.html</guid></item><item><title>What's new in Seastar - issue 4</title><link>https://makedist.com/posts/2024/08/31/whats-new-in-seastar-issue-4/</link><description>A summary of recent developments in the &lt;a href="https://seastar.io/"&gt;C++ Seastar project&lt;/a&gt;.</description><author>Noah Watkins</author><pubDate>Sat, 31 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://makedist.com/posts/2024/08/31/whats-new-in-seastar-issue-4/</guid></item><item><title>Running Huggingface Models with Llama.cpp and ollama</title><link>https://www.danielcorin.com/til/llama-cpp/running-huggingface-models/</link><description>Running Huggingface Models with Llama.cpp and ollama</description><author>Thought Eddies</author><pubDate>Fri, 30 Aug 2024 21:24:53 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/llama-cpp/running-huggingface-models/</guid></item><item><title>The time's traveling</title><link>https://deadlime.hu/en/2024/08/30/the-times-traveling/</link><description>Clock development is getting more complicated, time zones have arrived</description><author>deadlime</author><pubDate>Fri, 30 Aug 2024 21:04:18 GMT</pubDate><guid isPermaLink="true">https://deadlime.hu/en/2024/08/30/the-times-traveling/</guid></item><item><title>The secret inside One Million Checkboxes</title><link>https://nicolaiarocci.com/the-secret-inside-one-million-checkboxes/</link><description>&lt;blockquote&gt;
&lt;p&gt;A few days into making One Million Checkboxes I thought I’d been hacked. What was that doing in my database? A few hours later I was tearing up, proud of some brilliant teens.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;Full story &lt;a href="https://eieio.games/essays/the-secret-in-one-million-checkboxes/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;What a great story. Teenagers who are enthusiastic about hacking and coding and have lots of fun in creative ways. It reminds me so much of my teenage years, like when assembling a fake backdoor on Lorien, my first BBS, as a honeypot to attract local hackers so I could later reach out and get to know them&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Fri, 30 Aug 2024 10:52:11 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/the-secret-inside-one-million-checkboxes/</guid></item><item><title>OnlineOrNot Diaries 21</title><link>https://maxrozen.com/onlineornot-diaries-21</link><description>I was young, and needed to ship...</description><author>Max Rozen</author><pubDate>Fri, 30 Aug 2024 10:10:00 GMT</pubDate><guid isPermaLink="true">https://maxrozen.com/onlineornot-diaries-21</guid></item><item><title>On this day, August 30</title><link>https://stop.zona-m.net/2024/08/on-this-day-august-30/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2010 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Fri, 30 Aug 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/08/on-this-day-august-30/</guid></item><item><title>Deploy Strapi with Docker on Digital Ocean App Platform</title><link>https://www.tderflinger.com/en/deploy-strapi-digital-ocean-app-platform-docker</link><description>The headless open source content management system (CMS) Strapi can be dockerized and deployed via Docker to the cloud. In this article I want to show how to deploy Strapi to the Digital Ocean App Platform as a Docker container.</description><author>Thoughts by Thomas Derflinger</author><pubDate>Fri, 30 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.tderflinger.com/en/deploy-strapi-digital-ocean-app-platform-docker</guid></item><item><title>CTO as a Service</title><link>https://yasha.solutions/service/cto/</link><description>20 years of experience in marketing, product and technology to help develop your startup&amp;rsquo;s technology infrastructure</description><author>Yasha Solutions</author><pubDate>Fri, 30 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://yasha.solutions/service/cto/</guid></item><item><title>So Many Silver Landmines</title><link>https://taylor.town/silver-landmines</link><description>Silver bullets magically solve problems. Silver landmines magically create problems.</description><author>taylor.town</author><pubDate>Fri, 30 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/silver-landmines</guid></item><item><title>Upcoming talk at LSS Europe 2024</title><link>https://blog.gnoack.org/post/landlock-ioctl-talk-ann</link><description>&lt;p&gt;📢 I will give a talk about the recent changes in Landlock and its new
&lt;a href="https://wiki.gnoack.org/LandlockIoctlControl"&gt;support for restricting IOCTL
usage&lt;/a&gt; at the Linux
Security Summit Europe 2024 in Vienna:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Landlock security module lets Linux processes restrict what they
can do and puts developers in charge of defining appropriate
sandboxing policies for their programs. We will give a brief
overview over Landlock’s current features, recent developments, and
talk about what is next. We will discuss in more detail Landlock’s
new support for restricting the use of IOCTL and the design
considerations and trade-offs that went into it.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;🌍 Link: &lt;a href="https://sched.co/1ebVW"&gt;https://sched.co/1ebVW&lt;/a&gt;&lt;/p&gt;</description><author>Blog on blog.gnoack.org</author><pubDate>Thu, 29 Aug 2024 23:33:24 GMT</pubDate><guid isPermaLink="true">https://blog.gnoack.org/post/landlock-ioctl-talk-ann</guid></item><item><title>Infocom: The Documentary</title><link>https://nicolaiarocci.com/infocom-the-documentary/</link><description>&lt;p&gt;For nerds of my generation, Infocom is a legend. Today, I watched the long-time overdue &lt;em&gt;Infocom: The Documentary&lt;/em&gt; and I found it to be a gem.&lt;/p&gt;
&lt;p&gt;&lt;div style="padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;
      
    &lt;/div&gt;

&lt;!-- raw HTML omitted --&gt;&lt;/p&gt;
&lt;p&gt;With no commentary or narration but made up of the protagonists’ testimonies alone, it effectively evokes the excitement and enthusiasm around the early computer game industry (and software development in general) of those early years. It is also a cautionary tale about how easy it is to fall once you reach the peak&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Thu, 29 Aug 2024 16:15:42 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/infocom-the-documentary/</guid></item><item><title>Simple versus Complex</title><link>https://ilearnt.com/blog/simpleversuscomplex/</link><description>&lt;p&gt;It is very difficult to develop a simple piece of software and keep it simple.&lt;/p&gt;</description><author>I Learnt</author><pubDate>Thu, 29 Aug 2024 14:00:00 GMT</pubDate><guid isPermaLink="true">https://ilearnt.com/blog/simpleversuscomplex/</guid></item><item><title>Homelab monitoring using Grafana and Prometheus</title><link>https://danielpecos.com/2024/08/29/homelab-monitoring-using-grafana-and-prometheus/</link><description>&lt;img alt="" class="aligncenter" height="400" src="https://danielpecos.com/2024/08/29/homelab-monitoring-using-grafana-and-prometheus/assets/homelab_hu_1327fee1962ff781.webp" style="width: auto; height: auto;" width="400" /&gt;

&lt;p&gt;I have a homelab, mainly as a hobby but also as an environment for experimentation. It&amp;rsquo;s quite useful for self-hosting different services—not only because of the potential cost savings (although you do need to account for hardware costs and electricity) — but also because it allows you to develop skills that will be useful as a professional developer.&lt;/p&gt;
&lt;p&gt;And the best part? You don&amp;rsquo;t have to invest a lot of money — I didn’t. I started my homelab with a Raspberry Pi 3B, and for about a year, that was all I needed. It was capable of running HomeAssistant and Mosquitto well enough. Eventually, as I brought more services into my homelab, I had to expand. And guess what? I got a Raspberry Pi 4. So now I have two hosts in my homelab, and the most important question I had to answer was which node should run each service and what kind of availability I wanted.&lt;/p&gt;
&lt;p&gt;Docker was the solution I decided to go with because it allows me to easily describe services and their storage needs (using Docker Compose), and also because I can easily transfer services between hosts. Eventually, I even tried defining my homelab as a Docker Swarm cluster, although I recently decided to roll it back. But that’s a story for another post.&lt;/p&gt;
&lt;p&gt;Recently, I expanded my homelab yet again, now with a&amp;hellip; Raspberry Pi 5, as the RPi 4 was getting a little overloaded—not by CPU consumption, but because of memory. So having an extra node to balance that load was the best solution.&lt;/p&gt;
&lt;p&gt;At this moment, it&amp;rsquo;s quite tricky for me to know what maintenance each node requires unless I manually SSH into each one to check from time to time, which is far from ideal. I&amp;rsquo;m talking about system updates, free space on the different attached hard drives, SMART alerts they may raise, and so on.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s not scalable, so I needed to start automating. The first step, which is what this post covers, was to create dashboards and bring visibility to the hosts and services I’m running in my homelab.&lt;/p&gt;
&lt;h2 id="dashboards"&gt;Dashboards&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start by defining the software stack I&amp;rsquo;m going to set up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Grafana&lt;/strong&gt;: to create nice dashboards&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prometheus&lt;/strong&gt;: to scrape raw data from remote services and hosts that will be presented in the dashboards&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;prometheus-node-exporter&lt;/strong&gt;: a service that will be running on each node, exposing operating system metrics (CPU, memory, disk free, etc.) to Prometheus&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;cAdvisor&lt;/strong&gt;: a service that runs as a Docker container on each node, exposing details about the containers that the node is hosting&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Docker&lt;/strong&gt;: to run Dockerized services (all the described services except for prometheus-node-exporter, which runs natively on the host system)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This would be the way these components interact with each other:&lt;/p&gt;
&lt;pre class="mermaid"&gt;
  sequenceDiagram
  participant Grafana
  participant Prometheus
  participant prometheus-node-exporter
  participant cAdvisor
  participant docker

  par
    Note over prometheus-node-exporter, docker: These components run on remote hosts
    Prometheus -&amp;gt;&amp;gt; prometheus-node-exporter: Scrape node status
    Prometheus -&amp;gt;&amp;gt;+ cAdvisor: Scrape docker details
    cAdvisor -&amp;gt;&amp;gt;- docker: Introspect containers
  end
  Grafana -&amp;gt;&amp;gt; Prometheus: Display data
&lt;/pre&gt;

&lt;h3 id="how-to-run-these-services"&gt;How-to run these services&lt;/h3&gt;
&lt;h4 id="services-supporting-the-dashboards"&gt;Services supporting the dashboards&lt;/h4&gt;
&lt;p&gt;I chose the oldest Raspberry Pi that I had available (a Raspberry Pi 3B with 1GB RAM), flashed a fresh copy of Raspbian, and then installed Docker:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;sudo apt install docker.io docker-compose
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Next, I added my user to the Docker group:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;sudo usermod -aG docker youruser
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This sets the basis for running the rest of the services, starting with a Docker Compose file containing Grafana and Prometheus:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-yaml"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;services&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;prometheus&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;image&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;prom/prometheus&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;command&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- &lt;span style="color: #b83838;"&gt;'--config.file=/etc/prometheus/prometheus.yml'&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ports&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- &lt;span style="color: #444;"&gt;9090&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #444;"&gt;9090&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;restart&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;unless-stopped&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;volumes&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /homelab/volumes/monitoring_prometheus:/etc/prometheus&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- prometheus_data:/prometheus&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;grafana&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;image&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;grafana/grafana&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ports&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- &lt;span style="color: #444;"&gt;3000&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #444;"&gt;3000&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;restart&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;unless-stopped&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;volumes&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /homelab/volumes/monitoring_grafana:/etc/grafana/provisioning/datasources&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- grafana_data:/var/lib/grafana&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;volumes&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;prometheus_data&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;grafana_data&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;where &amp;lsquo;prometheus.yml&amp;rsquo; contains:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-yaml"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;global&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;scrape_interval&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;15s&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;scrape_timeout&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;10s&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;evaluation_interval&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;15s&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;alerting&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;alertmanagers&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;static_configs&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;targets&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #888;"&gt;[]&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;scheme&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;http&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;timeout&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;10s&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;api_version&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;v1&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;scrape_configs&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;&lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;job_name&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;prometheus&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;honor_timestamps&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #444; font-style: italic;"&gt;true&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;metrics_path&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;/metrics&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;scheme&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;http&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;static_configs&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;targets&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- localhost:9090&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;&lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;job_name&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;homelab_node_exporter&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;static_configs&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;targets&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- homelab:9100&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;&lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;job_name&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;homelab_docker_engines&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;static_configs&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;targets&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- homelab:9323&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;&lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;job_name&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;homelab_docker_containers&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;static_configs&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;targets&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- homelab:3080&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;&lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;job_name&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;external_app&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;static_configs&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;targets&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- remote.server:3080&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- remote.server:3081&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;basic_auth&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# This is an example on how to scrape data from a remote host that is behind HTTP Basic Auth&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;username&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;user&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# Replace this with a user of your choice!&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;password&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;XYZ&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# Replace this with a clear text password of your choice!&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(where &lt;code&gt;homelab&lt;/code&gt; is the DNS name or IP of the host your scrapping the data)&lt;/p&gt;
&lt;p&gt;and &lt;code&gt;datasource.yml&lt;/code&gt; contains:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-yaml"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;apiVersion&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #444;"&gt;1&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;datasources&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;&lt;/span&gt;- &lt;span style="color: #2838b0;"&gt;name&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;Prometheus&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;type&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;prometheus&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;url&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;http://prometheus:9090&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;isDefault&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #444; font-style: italic;"&gt;true&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;access&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;proxy&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;editable&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #444; font-style: italic;"&gt;true&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;With all these files in place, it&amp;rsquo;s now just a matter of creating the new containers with the following command (run it in the same directory where &lt;code&gt;docker-compose.yml&lt;/code&gt; is located):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;docker-compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you access the &lt;strong&gt;Prometheus&lt;/strong&gt; service URL now (replace &lt;code&gt;ip&lt;/code&gt; with the IP address of the machine where you just set up Grafana and Prometheus):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;http://ip:9090/targets
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;You should be able to see the list of configured targets, which should all be displayed as down. We’ve defined them in the configuration file, but they are not yet available to be scraped.&lt;/p&gt;
&lt;h4 id="services-exposing-data-to-be-scraped"&gt;Services exposing data to be scraped&lt;/h4&gt;
&lt;p&gt;Now, on every host that we&amp;rsquo;d need to monitor, we will need to:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;For hosts that are &lt;strong&gt;NOT exposed&lt;/strong&gt; to the internet, we can export docker metrics that will be read by &lt;strong&gt;Prometheus&lt;/strong&gt;. To do that, edit &lt;code&gt;/etc/docker/daemon.json&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-json"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #2838b0;"&gt;"metrics-addr"&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt; &lt;span style="color: #b83838;"&gt;"ip:9323"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and restart docker:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;sudo systemctl restart docker.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ol start="2"&gt;
&lt;li&gt;Run &lt;code&gt;prometheus-node-exporter&lt;/code&gt;:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;sudo apt install prometheus-node-exporter
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This will install it as a new service and start it right away.&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;&lt;strong&gt;Run a Docker container with cAdvisor&lt;/strong&gt; to expose container details to Prometheus. I chose to use Docker Compose for this as well:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-yaml"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;services&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;cadvisor&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;image&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;gcr.io/cadvisor/cadvisor:latest&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;restart&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;unless-stopped&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;volumes&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /:/rootfs:ro&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /var/run:/var/run:ro&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /sys:/sys:ro&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /var/lib/docker/:/var/lib/docker:ro&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /dev/disk/:/dev/disk:ro&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;devices&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /dev/kmsg&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ports&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- &lt;span style="color: #444;"&gt;3080&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #444;"&gt;8080&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;(Note: if the host is publicly available on Internet, remove the ports section from the &lt;code&gt;docker-compose.yml&lt;/code&gt; file above, there is an explanation below on how to secure this data)&lt;/p&gt;
&lt;p&gt;And as we did before:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;docker-compose up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;If you reload the &lt;strong&gt;Prometheus&lt;/strong&gt; service URL (that you&amp;rsquo;ve set up in the previous section), you should now see the configured targets as healthy.&lt;/p&gt;
&lt;p&gt;We are now ready to start creating dashboards.&lt;/p&gt;
&lt;h3 id="creating-the-dashboards"&gt;Creating the dashboards&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set up your admin account in Grafana&lt;/strong&gt;: Begin by configuring your admin account for Grafana to manage access and settings.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Add Prometheus as a data source&lt;/strong&gt;: You’ll need to add Prometheus as a data source in Grafana. Since Prometheus is running as part of the same Docker stack as Grafana, you can use &lt;code&gt;http://prometheus:9090&lt;/code&gt; as the URL. 







  


&lt;img alt="" class="" height="804" src="https://danielpecos.com/2024/08/29/homelab-monitoring-using-grafana-and-prometheus/assets/grafana-prometheus-datasource_hu_c9e03bece66b1fd9.webp" style="width: auto; height: auto;" width="500" /&gt;
&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Import the cAdvisor exporter dashboard&lt;/strong&gt;: Use the cAdvisor exporter dashboard (ID: 14282) to display node details. You can find it at &lt;a href="https://grafana.com/grafana/dashboards/14282-cadvisor-exporter/"&gt;this link&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Import the Node Exporter Full dashboard&lt;/strong&gt;: Use the Node Exporter Full dashboard (ID: 1860) to display Docker container details, using &lt;a href="https://grafana.com/grafana/dashboards/1860-node-exporter-full/"&gt;this link&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="securing-access-to-the-raw-data"&gt;Securing access to the raw data&lt;/h3&gt;
&lt;p&gt;Up to this point, we haven’t focused on security because we’ve been monitoring hosts and services within our homelab, which isn’t directly accessible from the internet.&lt;/p&gt;
&lt;p&gt;However, if we want to monitor an external service using our locally hosted Grafana instance, we need to implement security measures. The &lt;code&gt;prometheus-node-exporter&lt;/code&gt; and &lt;code&gt;cAdvisor&lt;/code&gt; could be exposing sensitive data that we don’t want to be publicly accessible.&lt;/p&gt;
&lt;p&gt;These are the steps that you should follow to secure your data (using a reverse proxy applying &lt;strong&gt;HTTP Basic Auth&lt;/strong&gt;):&lt;/p&gt;
&lt;p&gt;Create a new Docker stack (&lt;code&gt;docker-compose.yml&lt;/code&gt;) with the following content (Note: in this case, &lt;code&gt;cAdvisor&lt;/code&gt; container is not directly exposing any port):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-yaml"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;services&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;cadvisor&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;image&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;gcr.io/cadvisor/cadvisor:latest&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;restart&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;unless-stopped&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;volumes&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /:/rootfs:ro&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /var/run:/var/run:ro&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /sys:/sys:ro&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /var/lib/docker/:/var/lib/docker:ro&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /dev/disk/:/dev/disk:ro&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;devices&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- /dev/kmsg&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# https://github.com/dtan4/nginx-basic-auth-proxy&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;nginx-cadvisor&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;#image: quay.io/dtan4/nginx-basic-auth-proxy:latest&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;build&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;context&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;.&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ports&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- &lt;span style="color: #444;"&gt;3080&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #444;"&gt;80&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;#- 8090:8090&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;environment&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- BASIC_AUTH_USERNAME=user&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# Replace this with the username of your choice&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- BASIC_AUTH_PASSWORD=XYZ&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# Replace this with a clear text password of your choice!&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- PROXY_PASS=http://cadvisor:8080&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;  &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;nginx-node-exporter&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;#image: quay.io/dtan4/nginx-basic-auth-proxy:latest&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;build&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;context&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt; &lt;/span&gt;.&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ports&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- &lt;span style="color: #444;"&gt;3081&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #444;"&gt;80&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;#- 8090:8090&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;    &lt;/span&gt;&lt;span style="color: #2838b0;"&gt;environment&lt;/span&gt;&lt;span style="color: #888;"&gt;:&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- BASIC_AUTH_USERNAME=user&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# Replace this with the username of your choice&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- BASIC_AUTH_PASSWORD=XYZ&lt;span style="color: #a89028;"&gt; &lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# Replace this with a clear text password of your choice!&lt;/span&gt;&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #a89028;"&gt;      &lt;/span&gt;- PROXY_PASS=http://172.17.0.1:9100&lt;span style="color: #a89028;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Additionally, create a &lt;code&gt;Dockerfile&lt;/code&gt; in the same directory with the following content:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-docker"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;FROM&lt;/span&gt;&lt;span style="color: #b83838;"&gt; nginx:1.11.9-alpine&lt;/span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# for htpasswd command&lt;/span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;RUN&lt;/span&gt; apk add --no-cache --update &lt;span style="color: #709030;"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #709030;"&gt;&lt;/span&gt;      apache2-utils&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;RUN&lt;/span&gt; rm -f /etc/nginx/conf.d/*&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ENV&lt;/span&gt; SERVER_NAME example.com&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ENV&lt;/span&gt; PORT &lt;span style="color: #444;"&gt;80&lt;/span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ENV&lt;/span&gt; CLIENT_MAX_BODY_SIZE 1m&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ENV&lt;/span&gt; PROXY_READ_TIMEOUT 60s&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ENV&lt;/span&gt; WORKER_PROCESSES auto&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;COPY&lt;/span&gt; files/run.sh /&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;COPY&lt;/span&gt; files/nginx.conf.tmpl /&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;RUN&lt;/span&gt; &lt;span style="color: #666;"&gt;[&lt;/span&gt;&lt;span style="color: #b83838;"&gt;"chmod"&lt;/span&gt;, &lt;span style="color: #b83838;"&gt;"+x"&lt;/span&gt;, &lt;span style="color: #b83838;"&gt;"/run.sh"&lt;/span&gt;&lt;span style="color: #666;"&gt;]&lt;/span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# use SIGQUIT for graceful shutdown&lt;/span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #888; font-style: italic;"&gt;# c.f. http://nginx.org/en/docs/control.html&lt;/span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;STOPSIGNAL&lt;/span&gt;&lt;span style="color: #b83838;"&gt; SIGQUIT&lt;/span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="background-color: #a848a8;"&gt;&lt;/span&gt;&lt;span style="color: #2838b0;"&gt;ENTRYPOINT&lt;/span&gt; &lt;span style="color: #888;"&gt;[&lt;/span&gt;&lt;span style="color: #b83838;"&gt;"/run.sh"&lt;/span&gt;&lt;span style="color: #888;"&gt;]&lt;/span&gt;&lt;span style="background-color: #a848a8;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;where &lt;code&gt;files/run.sh&lt;/code&gt; contains:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #289870;"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #289870;"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #388038;"&gt;set&lt;/span&gt; -e
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;if&lt;/span&gt; &lt;span style="color: #666;"&gt;[&lt;/span&gt; -z &lt;span style="color: #b04040;"&gt;$BASIC_AUTH_USERNAME&lt;/span&gt; &lt;span style="color: #666;"&gt;]&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt; &lt;span style="color: #2838b0;"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #388038;"&gt;echo&lt;/span&gt; &amp;gt;&lt;span style="color: #888;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #444;"&gt;2&lt;/span&gt; &lt;span style="color: #b83838;"&gt;"BASIC_AUTH_USERNAME must be set"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #388038;"&gt;exit&lt;/span&gt; &lt;span style="color: #444;"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;if&lt;/span&gt; &lt;span style="color: #666;"&gt;[&lt;/span&gt; -z &lt;span style="color: #b04040;"&gt;$BASIC_AUTH_PASSWORD&lt;/span&gt; &lt;span style="color: #666;"&gt;]&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt; &lt;span style="color: #2838b0;"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #388038;"&gt;echo&lt;/span&gt; &amp;gt;&lt;span style="color: #888;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #444;"&gt;2&lt;/span&gt; &lt;span style="color: #b83838;"&gt;"BASIC_AUTH_PASSWORD must be set"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #388038;"&gt;exit&lt;/span&gt; &lt;span style="color: #444;"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;if&lt;/span&gt; &lt;span style="color: #666;"&gt;[&lt;/span&gt; -z &lt;span style="color: #b04040;"&gt;$PROXY_PASS&lt;/span&gt; &lt;span style="color: #666;"&gt;]&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt; &lt;span style="color: #2838b0;"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #388038;"&gt;echo&lt;/span&gt; &amp;gt;&lt;span style="color: #888;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #444;"&gt;2&lt;/span&gt; &lt;span style="color: #b83838;"&gt;"PROXY_PASS must be set"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #388038;"&gt;exit&lt;/span&gt; &lt;span style="color: #444;"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;htpasswd -bBc /etc/nginx/.htpasswd &lt;span style="color: #b04040;"&gt;$BASIC_AUTH_USERNAME&lt;/span&gt; &lt;span style="color: #b04040;"&gt;$BASIC_AUTH_PASSWORD&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;sed &lt;span style="color: #709030;"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #709030;"&gt;&lt;/span&gt;  -e &lt;span style="color: #b83838;"&gt;"s/##CLIENT_MAX_BODY_SIZE##/&lt;/span&gt;&lt;span style="color: #b04040;"&gt;$CLIENT_MAX_BODY_SIZE&lt;/span&gt;&lt;span style="color: #b83838;"&gt;/g"&lt;/span&gt; &lt;span style="color: #709030;"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #709030;"&gt;&lt;/span&gt;  -e &lt;span style="color: #b83838;"&gt;"s/##PROXY_READ_TIMEOUT##/&lt;/span&gt;&lt;span style="color: #b04040;"&gt;$PROXY_READ_TIMEOUT&lt;/span&gt;&lt;span style="color: #b83838;"&gt;/g"&lt;/span&gt; &lt;span style="color: #709030;"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #709030;"&gt;&lt;/span&gt;  -e &lt;span style="color: #b83838;"&gt;"s/##WORKER_PROCESSES##/&lt;/span&gt;&lt;span style="color: #b04040;"&gt;$WORKER_PROCESSES&lt;/span&gt;&lt;span style="color: #b83838;"&gt;/g"&lt;/span&gt; &lt;span style="color: #709030;"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #709030;"&gt;&lt;/span&gt;  -e &lt;span style="color: #b83838;"&gt;"s/##SERVER_NAME##/&lt;/span&gt;&lt;span style="color: #b04040;"&gt;$SERVER_NAME&lt;/span&gt;&lt;span style="color: #b83838;"&gt;/g"&lt;/span&gt; &lt;span style="color: #709030;"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #709030;"&gt;&lt;/span&gt;  -e &lt;span style="color: #b83838;"&gt;"s/##PORT##/&lt;/span&gt;&lt;span style="color: #b04040;"&gt;$PORT&lt;/span&gt;&lt;span style="color: #b83838;"&gt;/g"&lt;/span&gt; &lt;span style="color: #709030;"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #709030;"&gt;&lt;/span&gt;  -e &lt;span style="color: #b83838;"&gt;"s|##PROXY_PASS##|&lt;/span&gt;&lt;span style="color: #b04040;"&gt;$PROXY_PASS&lt;/span&gt;&lt;span style="color: #b83838;"&gt;|g"&lt;/span&gt; &lt;span style="color: #709030;"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #709030;"&gt;&lt;/span&gt;  nginx.conf.tmpl &amp;gt; /etc/nginx/nginx.conf
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #388038;"&gt;exec&lt;/span&gt; nginx -g &lt;span style="color: #b83838;"&gt;"daemon off;"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;and &lt;code&gt;files/nginx.conf.tmpl&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-nginx"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;user&lt;/span&gt; &lt;span style="color: #b83838;"&gt;nginx&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;worker_processes&lt;/span&gt; &lt;span style="color: #888; font-style: italic;"&gt;##WORKER_PROCESSES##;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888; font-style: italic;"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #b83838;"&gt;error_log&lt;/span&gt; &lt;span style="color: #b83838;"&gt;/dev/stdout&lt;/span&gt; &lt;span style="color: #b83838;"&gt;info&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;pid&lt;/span&gt; &lt;span style="color: #b83838;"&gt;/var/run/nginx.pid&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;events&lt;/span&gt; &lt;span style="color: #888;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #2838b0;"&gt;worker_connections&lt;/span&gt; &lt;span style="color: #444;"&gt;1024&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #2838b0;"&gt;http&lt;/span&gt; &lt;span style="color: #888;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #2838b0;"&gt;access_log&lt;/span&gt; &lt;span style="color: #b83838;"&gt;/dev/stdout&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #2838b0;"&gt;server&lt;/span&gt; &lt;span style="color: #888;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #2838b0;"&gt;listen&lt;/span&gt; &lt;span style="color: #888; font-style: italic;"&gt;##PORT##;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #b83838;"&gt;server_name&lt;/span&gt; &lt;span style="color: #888; font-style: italic;"&gt;##SERVER_NAME##;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888; font-style: italic;"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #b83838;"&gt;client_max_body_size&lt;/span&gt; &lt;span style="color: #888; font-style: italic;"&gt;##CLIENT_MAX_BODY_SIZE##;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888; font-style: italic;"&gt;&lt;/span&gt;    &lt;span style="color: #b83838;"&gt;proxy_read_timeout&lt;/span&gt; &lt;span style="color: #888; font-style: italic;"&gt;##PROXY_READ_TIMEOUT##;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888; font-style: italic;"&gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #b83838;"&gt;include&lt;/span&gt; &lt;span style="color: #b83838;"&gt;/etc/nginx/conf.d/*.conf&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #2838b0;"&gt;location&lt;/span&gt; &lt;span style="color: #b83838;"&gt;/&lt;/span&gt; &lt;span style="color: #888;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #2838b0;"&gt;proxy_pass&lt;/span&gt; &lt;span style="color: #888; font-style: italic;"&gt;##PROXY_PASS##;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888; font-style: italic;"&gt;&lt;/span&gt;      &lt;span style="color: #b83838;"&gt;auth_basic&lt;/span&gt; &lt;span style="color: #b83838;"&gt;"Restricted"&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #2838b0;"&gt;auth_basic_user_file&lt;/span&gt; &lt;span style="color: #b83838;"&gt;/etc/nginx/.htpasswd&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #2838b0;"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color: #b83838;"&gt;X-Forwarded-Host&lt;/span&gt; &lt;span style="color: #b04040;"&gt;$host&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #888; font-style: italic;"&gt;# Do not pass Authorization header to destination
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888; font-style: italic;"&gt;&lt;/span&gt;      &lt;span style="color: #2838b0;"&gt;proxy_set_header&lt;/span&gt; &lt;span style="color: #b83838;"&gt;Authorization&lt;/span&gt; &lt;span style="color: #b83838;"&gt;""&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #888;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #888;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #2838b0;"&gt;server&lt;/span&gt; &lt;span style="color: #888;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #2838b0;"&gt;listen&lt;/span&gt; &lt;span style="color: #444;"&gt;8090&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #2838b0;"&gt;location&lt;/span&gt; &lt;span style="color: #b83838;"&gt;/nginx_status&lt;/span&gt; &lt;span style="color: #888;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #2838b0;"&gt;stub_status&lt;/span&gt; &lt;span style="color: #b85820;"&gt;on&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #2838b0;"&gt;access_log&lt;/span&gt; &lt;span style="color: #b85820;"&gt;off&lt;/span&gt;&lt;span style="color: #888;"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #888;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #888;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #888;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now, you can build and run the new docker stack:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;docker compose -f docker-compose.yml build
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;docker compose -f docker-compose.yml up -d
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The last step will be to secure &lt;code&gt;prometheus-node-exporter&lt;/code&gt; by restricting the IP range from which data can be retrieved to the internal Docker network, by editing the file &lt;code&gt;/etc/default/prometheus-node-exporter&lt;/code&gt;, adding:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ARGS="--web.listen-address=172.17.0.1:9100"
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;(this port is also secured by &lt;strong&gt;HTTP Basic Auth&lt;/strong&gt; from the docker file above)&lt;/p&gt;
&lt;p&gt;And then restarting the service:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-sh"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;sudo systemctl restart prometheus-node-exporter.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note: Don&amp;rsquo;t forget to align user and password with the prometheus targets config file that we saw before.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Setting up a homelab can be a rewarding experience that offers both practical benefits and opportunities for learning. By using tools like Docker, Grafana, Prometheus, and various exporters, you can effectively monitor and manage your services while gaining valuable skills that are applicable to professional development work.&lt;/p&gt;
&lt;p&gt;In this guide, we walked through the process of creating dashboards for monitoring and discussed the importance of securing access to sensitive data, especially when extending your monitoring capabilities to external services. With these steps, you can ensure your homelab is both efficient and secure, providing a robust environment for experimentation and self-hosting.&lt;/p&gt;
&lt;p&gt;Whether you&amp;rsquo;re just starting out with a single Raspberry Pi or expanding your setup with multiple hosts, remember that the key to a successful homelab is continuous learning and adaptation. Keep exploring new tools and techniques, and enjoy the journey of building and managing your own digital ecosystem.&lt;/p&gt;
&lt;p&gt;







  


&lt;img alt="" class="aligncenter" height="564" src="https://danielpecos.com/2024/08/29/homelab-monitoring-using-grafana-and-prometheus/assets/image_8ZR_hu_6a2ecc5fad56a76b.webp" style="width: auto; height: auto;" width="1024" /&gt;









  


&lt;img alt="" class="aligncenter" height="535" src="https://danielpecos.com/2024/08/29/homelab-monitoring-using-grafana-and-prometheus/assets/image_EUU_hu_73518f0eb6ea5b6b.webp" style="width: auto; height: auto;" width="1024" /&gt;
&lt;/p&gt;</description><author>GeekWare - Daniel Pecos Martínez</author><pubDate>Thu, 29 Aug 2024 11:27:00 GMT</pubDate><guid isPermaLink="true">https://danielpecos.com/2024/08/29/homelab-monitoring-using-grafana-and-prometheus/</guid></item><item><title>Playtime</title><link>https://iam.mt/playtime/</link><description>I am planning on switching my playtime to something new. Over the next couple of months, that's what my quest is going to be.</description><author>Mohnish Thallavajhula</author><pubDate>Thu, 29 Aug 2024 10:38:33 GMT</pubDate><guid isPermaLink="true">https://iam.mt/playtime/</guid></item><item><title>🧘‍♂️ A comprehensive guide to Vipassana as experienced by tech entrepreneur (super practical!)</title><link>https://www.ednevsky.blog/p/a-comprehensive-guide-to-vipassana</link><description>I absolutely loved this experience and want to give back by sharing it</description><author>Ednevsky Blog</author><pubDate>Thu, 29 Aug 2024 10:32:52 GMT</pubDate><guid isPermaLink="true">https://www.ednevsky.blog/p/a-comprehensive-guide-to-vipassana</guid></item><item><title>On this day, August 29</title><link>https://stop.zona-m.net/2024/08/on-this-day-august-29/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2011 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Thu, 29 Aug 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/08/on-this-day-august-29/</guid></item><item><title>Autocomplete is not all you need: Why Cursor and Zed are going to dominate</title><link>https://zackproser.com/blog/autocomplete-is-not-all-you-need</link><description>AI assisted developer tooling is not created equally...</description><author>Zachary Proser</author><pubDate>Thu, 29 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/autocomplete-is-not-all-you-need</guid></item><item><title>Don't just disagree, ask why</title><link>https://rachitsingh.com/dont-disagree-ask-why/</link><description>&lt;p&gt;When disagreeing with someone about a decision or a fact, it's useful to ask someone why rather than just presenting your own view. It reorients the discussion as the team vs the problem, and gives everyone space to acknowledge why they might be wrong.&lt;/p&gt;
&lt;h3 id="curiosity"&gt;Curiosity&lt;/h3&gt;
&lt;img src="https://rachitsingh.com/ted_lasso_darts.png" /&gt;
&lt;p&gt;Ted Lasso is back for a 4th season, which made me think of this moment in the first&lt;sup class="footnote-reference" id="fr-1-1"&gt;&lt;a href="https://rachitsingh.com/dont-disagree-ask-why/#fn-1"&gt;1&lt;/a&gt;&lt;/sup&gt;. One of my favorite scenes is &lt;a href="https://www.youtube.com/watch?v=5x0PzUoJS-U"&gt;when Ted plays darts with Rupert&lt;/a&gt;. Here's a transcript for the video-weary:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You know Rupert, guys have underestimated my entire life. And for years I never understood why. It used to really bother me. But then one day I was driving my little boy to school and I saw this quote by Walt Whitman and it was painted on the wall there that said "Be curious. Not judgmental."&lt;sup class="footnote-reference" id="fr-2-1"&gt;&lt;a href="https://rachitsingh.com/dont-disagree-ask-why/#fn-2"&gt;2&lt;/a&gt;&lt;/sup&gt; I like that. &lt;strong&gt;thwack&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;So I get back in my car and I'm driving to work and all of sudden it hits me. All them fellas that used to belittle me, not a single one of 'em was curious. You know they thought they had everything figured out, so they judged everything, and they judged everyone. And I realized that their underestimating me, who I was had nothing to do with it. Because if they were curious, they would ask questions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;You know. Questions like, &lt;em&gt;have you played a lot of darts, Ted?&lt;/em&gt; &lt;strong&gt;thwack&lt;/strong&gt;. To which I would have answered: &lt;em&gt;Yes sir. Every Sunday afternoon at a sports bar with my father from age 10 til I was 16 when he passed away.&lt;/em&gt;
Barbecue sauce. &lt;strong&gt;thwack&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The scene has a lot of emotional appeal; everyone likes when the main character is secretly more skilled than they're letting on. There's a TV Trope for it: &lt;a href="https://tvtropes.org/pmwiki/pmwiki.php/Main/IAmNotLeftHanded"&gt;I Am Not Left-Handed&lt;/a&gt;, which Ted Lasso even calls out directly:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ted: Oh, wait a second. I forgot I'm left handed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Outside of avoiding judgement, &lt;a href="https://www.benkuhn.net/listen/"&gt;being curious is the start of good listening&lt;/a&gt;&lt;sup class="footnote-reference" id="fr-3-1"&gt;&lt;a href="https://rachitsingh.com/dont-disagree-ask-why/#fn-3"&gt;3&lt;/a&gt;&lt;/sup&gt;. Repeating back what someone is saying in your own words is both communicating that you're actively listening and something that you naturally do when you're curious.&lt;/p&gt;
&lt;h3 id="one-way-to-disagree"&gt;One way to disagree&lt;/h3&gt;
&lt;p&gt;I think there's one further version of this idea, which is helpful when navigating conflict. It's pretty straightforward: don't just disagree, ask why.&lt;/p&gt;
&lt;p&gt;There are a lot of places where basic disagreements come up. For example, at trivia, you might think: "I know for sure that Jane Addams was born in 1860" and your friend might be equally confident that she was born in the 1880s.&lt;/p&gt;
&lt;p&gt;The least useful way to communicate is to just be confident that the other person is wrong (e.g. "No, she was born in 1860."). Even if you've just finished reading a book on her life, you can do a better job of communicating than just shutting down their idea with a "that's wrong". A lot of people will change their mind if you ask a question like: "Why do you think so? Where did you read that?". If their memory is vague ("e.g. I remember reading about her after the Civil War section") and yours is precise ("I was reading a book last week that mentioned that she was born just before the Civil War in Illinois to a family with 8 children"), in the vast majority of cases they will acknowledge that you're probably right&lt;sup class="footnote-reference" id="fr-4-1"&gt;&lt;a href="https://rachitsingh.com/dont-disagree-ask-why/#fn-4"&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;This method is helpful in contexts where the stakes are low (who wants to have a fight about a trivia question?), but also applies to high stakes decisions, as well. &lt;strong&gt;By asking for the source of their decision or knowledge, you're communicating that you are a team trying to find the right solution&lt;/strong&gt;. This isn't a passive, inassertive, or conflict-avoidant way to communicate - it's just acknowledging that they could have a self-consistent explanation that comes to a different conclusion.&lt;/p&gt;
&lt;p&gt;Of course, it also helps you avoid looking silly by being dead-certain about something you're wrong about.&lt;/p&gt;
&lt;p&gt;Another reason to communicate this way (again, if it's useful to your particular context) is that it gives some people who have different unspoken assumptions a way to communicate. For example, let's say you're hanging out in a left-leaning circle, and someone says something like "unions are bad". You might at first just think that they are more conservative-leaning or at least in favor of business deregulation. But people make mistakes in communicating, especially around unspoken assumptions. If you ask them why they think that instead of moving on with your day, you could learn that they're talking about public sector unions, like police unions, because private unions being good are, for them, a given. You can learn a lot from asking people about things that seem obviously wrong.&lt;/p&gt;
&lt;p&gt;You can apply this approach to miniscule pointless conflicts as well. For example, if there's a disagreement about who lost the TV remote, by asking "why" you might be able to piece together a chain of observations which could lead to the remote. You might also discover that your partner is frustrated with your lack of organization, or inability to work with their organization scheme.&lt;/p&gt;
&lt;h3 id="finally-a-decision"&gt;Finally, a decision&lt;/h3&gt;
&lt;p&gt;This isn't a foolproof way to communicate, and eventually someone (you, the group, etc.) will have to make a decision on which way to go. Or, you can agree to disagree. Both are actually much easier when you both understand the merits of each other's arguments. &lt;strong&gt;It is a lot easier to agree to "50-50" two reasonable-looking options than to feel like your opinion isn't being considered or you're being dismissed out of turn.&lt;/strong&gt;&lt;/p&gt;
&lt;footer class="footnotes"&gt;
&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-1"&gt;
&lt;p&gt;It never quite lived up to that standard again, but it is still a very enjoyable watch through S3. &lt;a href="https://rachitsingh.com/dont-disagree-ask-why/#fr-1-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-2"&gt;
&lt;p&gt;According to Snopes, the quote isn't by Walt Whitman, but it's an easier story to tell that way. &lt;a href="https://rachitsingh.com/dont-disagree-ask-why/#fr-2-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-3"&gt;
&lt;p&gt;Ben's blog has some useful examples oriented around being a better listener. The core idea is helpful, but the takeaway is the one you always hear as a chronically solution-oriented person: just listen instead of offering solutions. &lt;a href="https://rachitsingh.com/dont-disagree-ask-why/#fr-3-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-4"&gt;
&lt;p&gt;And I should just say - this is just one way of communicating. In certain friend groups and certain cultures, it's more accepted to just reject someone's opinion you disagree with - it's expected, even. It can just be really abrasive to encounter that in a group where the norm is different. If someone is arguing in bad faith (i.e. is not really searching for the truth), this obviously doesn't work. But it can work in most peer-to-peer and close manager-to-worker relationships. &lt;a href="https://rachitsingh.com/dont-disagree-ask-why/#fr-4-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/footer&gt;</description><author>Rachit Singh</author><pubDate>Thu, 29 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://rachitsingh.com/dont-disagree-ask-why/</guid></item><item><title>Dynamic pricing, not discriminatory pricing</title><link>https://evanfields.net/Dynamic-Pricing/</link><description>There’s a great bagel shop near my house that has consistently long lines on weekend mornings. This is a market failure: if the line is predictably and consistently long, then the price in dollars is not high enough. The bagel shop could charge more on weekend mornings, which makes them better off; the average customer would then pay more in dollars but less in time.1 The mixture of customers might change. In a higher price regime, people who were previously excluded by the high time cost of the line (busy parents?) might opt in, while people who can’t afford the new higher price opt out. &amp;#8617;</description><author>Evan Fields</author><pubDate>Thu, 29 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://evanfields.net/Dynamic-Pricing/</guid></item><item><title>HashMap in 25 lines of C</title><link>https://xnacly.me/posts/2024/c-hash-map/</link><description>Minimal hash table implementation</description><author>xnacly - blog</author><pubDate>Thu, 29 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xnacly.me/posts/2024/c-hash-map/</guid></item><item><title>Upload Multiple Images with FastHTML</title><link>https://www.danielcorin.com/til/fasthtml/upload-multiple-images/</link><description>Upload Multiple Images with FastHTML</description><author>Thought Eddies</author><pubDate>Wed, 28 Aug 2024 22:27:38 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/fasthtml/upload-multiple-images/</guid></item><item><title>I spent an evening on a fictitious web</title><link>https://paul.kinlan.me/fictitious-web/</link><description>Experimented with WebSim, a simulated web environment, creating sites like a personal blog, timezone converter, interactive globe, and a travel site. The experience was reminiscent of the early web's playful exploration and highlighted WebSim's potential for creativity and interactive experiences.</description><author>Modern Web Development with Chrome</author><pubDate>Wed, 28 Aug 2024 17:44:00 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/fictitious-web/</guid></item><item><title>How I learned to stop worrying and love userspace networking</title><link>https://friendshipcastle.zip/blog/glaceon?utm_campaign=feed&amp;utm_source=blog.rss</link><description>Userspace wireguard is fun!</description><author>Twilight Sparkle's Friendship Castle</author><pubDate>Wed, 28 Aug 2024 16:00:00 GMT</pubDate><guid isPermaLink="true">https://friendshipcastle.zip/blog/glaceon?utm_campaign=feed&amp;utm_source=blog.rss</guid></item><item><title>On this day, August 28</title><link>https://stop.zona-m.net/2024/08/on-this-day-august-28/</link><description>&lt;p&gt;Interesting stuff that happened on this day, between 2010 and 2023.&lt;/p&gt;</description><author>Welcome to Marco Fioretti's website! on Stop at Zona-M</author><pubDate>Wed, 28 Aug 2024 04:00:00 GMT</pubDate><guid isPermaLink="true">https://stop.zona-m.net/2024/08/on-this-day-august-28/</guid></item><item><title>Getting the related work items for an Azure Pipeline run in a monorepo</title><link>https://tiberriver256.github.io/devops/azure-devops-pipelines-related-work-items-monorepo/</link><description>&lt;h2 id="the-problem"&gt;The problem..&lt;/h2&gt;

&lt;p&gt;I absolutely love this little &lt;a href="https://learn.microsoft.com/en-us/azure/devops/pipelines/process/environments?view=azure-devops#view-deployment-history"&gt;‘related work items’&lt;/a&gt;
feature in Azure DevOps pipelines:&lt;/p&gt;

&lt;p&gt;&lt;img alt="" src="https://res.cloudinary.com/dhff6zdnc/image/fetch/c_limit,w_800,q_auto,f_auto/https://tiberriver256.github.io/images/azure-devops-pipelines-related-work-items-monorepo/related-work-items-button.png" /&gt;&lt;/p&gt;

&lt;p&gt;You can click on it and quickly get a view of the work items associated with that pipeline:&lt;/p&gt;

&lt;p&gt;&lt;img alt="" src="https://res.cloudinary.com/dhff6zdnc/image/fetch/c_limit,w_800,q_auto,f_auto/https://tiberriver256.github.io/images/azure-devops-pipelines-related-work-items-monorepo/related-work-items-view.png" /&gt;&lt;/p&gt;

&lt;p&gt;This information is beneficial in two ways:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It is useful information for the person running a deployment&lt;/li&gt;
  &lt;li&gt;It can be used via ADO APIs to block deployments if related stories are in a certain status&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So… what’s the problem with this feature?&lt;/p&gt;

&lt;p&gt;It doesn’t support monorepos 😭😭😭&lt;/p&gt;

&lt;h2 id="what-happens-with-monorepos"&gt;What happens with monorepos?&lt;/h2&gt;

&lt;p&gt;Let’s say I have a repository set up with the following folder structure and two azure pipelines files:&lt;/p&gt;

&lt;p&gt;&lt;img alt="" src="https://res.cloudinary.com/dhff6zdnc/image/fetch/c_limit,w_800,q_auto,f_auto/https://tiberriver256.github.io/images/azure-devops-pipelines-related-work-items-monorepo/the-setup.png" /&gt;&lt;/p&gt;

&lt;p&gt;Then let’s say I commit to the main branch twice. Each commit adds a single &lt;code class="language-plaintext highlighter-rouge"&gt;index.js&lt;/code&gt; file to
each folder:&lt;/p&gt;

&lt;p&gt;&lt;img alt="" src="https://res.cloudinary.com/dhff6zdnc/image/fetch/c_limit,w_800,q_auto,f_auto/https://tiberriver256.github.io/images/azure-devops-pipelines-related-work-items-monorepo/the-work.png" /&gt;&lt;/p&gt;

&lt;p&gt;Each of those files trigger their own build runs. When I view the related work items for each though.. this happens:&lt;/p&gt;

&lt;p&gt;&lt;img alt="" src="https://res.cloudinary.com/dhff6zdnc/image/fetch/c_limit,w_800,q_auto,f_auto/https://tiberriver256.github.io/images/azure-devops-pipelines-related-work-items-monorepo/the-problem.png" /&gt;&lt;/p&gt;

&lt;h2 id="does-microsoft-plan-to-add-monorepo-support"&gt;Does Microsoft plan to add monorepo support?&lt;/h2&gt;

&lt;p&gt;No… even though I’d think there would be plenty of support for this,
the idea has never gotten any &lt;a href="https://stackoverflow.com/a/68729663/3317144"&gt;traction&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id="lets-find-a-solution"&gt;Let’s find a solution&lt;/h2&gt;

&lt;p&gt;It appears from the Azure DevOps REST API documentation that we should be able to do this ourselves…
&lt;strong&gt;IF&lt;/strong&gt; we are associating work items to our commits (via PR or otherwise). Which I so happen to be
doint already so the following method looks pretty promising:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Get ALL the commits related to a build pipeline via the &lt;a href="https://learn.microsoft.com/en-us/rest/api/azure/devops/build/builds/get-build-changes?view=azure-devops-rest-7.1"&gt;Get Build Changes&lt;/a&gt; endpoint.&lt;/li&gt;
  &lt;li&gt;Filter those commits down to the ones we want using the &lt;a href="https://learn.microsoft.com/en-us/rest/api/azure/devops/git/commits/get-commits?view=azure-devops-rest-7.1&amp;amp;tabs=HTTP"&gt;Get Commits&lt;/a&gt; endpoint using:
    &lt;ol&gt;
      &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;searchCriteria.itemPath&lt;/code&gt; - to exclude commits that aren’t in our trigger&lt;/li&gt;
      &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;searchCriteria.includeWorkItems&lt;/code&gt; - to include the work items associated with the commits&lt;/li&gt;
    &lt;/ol&gt;
  &lt;/li&gt;
  &lt;li&gt;Pull the work item ids from Step #2 to do whatever I want with&lt;/li&gt;
&lt;/ol&gt;

&lt;h3 id="powershell-time"&gt;PowerShell time!&lt;/h3&gt;

&lt;p&gt;Following the above strategy I &lt;a href="https://gist.github.com/Tiberriver256/99452a6bd254327acceb0405e34d2230"&gt;wrote up a quick PowerShell script&lt;/a&gt; that seems to get me everything I need:&lt;/p&gt;

&lt;div class="language-powershell highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="c"&gt;#Requires -PSEdition Core&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="cm"&gt;&amp;lt;#
&lt;/span&gt;&lt;span class="cs"&gt;.SYNOPSIS&lt;/span&gt;&lt;span class="cm"&gt;
    Retrieves distinct commits and associated work items for a build pipeline... with support for monorepos!
    See the blog post at https://tiberriver256.github.io/DevOps/azure-devops-pipelines-related-work-items-monorepo for more information.
&lt;/span&gt;&lt;span class="cs"&gt;.DESCRIPTION&lt;/span&gt;&lt;span class="cm"&gt;
    This script retrieves the distinct commits and associated work items for a build pipeline. It requires the following parameters:
    - BuildId: The ID of the build. If not provided, it uses the value of the environment variable BUILD_BUILDID.
    - CollectionUri: The URI of the collection. If not provided, it uses the value of the environment variable SYSTEM_COLLECTIONURI.
    - ProjectId: The ID of the project. If not provided, it uses the value of the environment variable SYSTEM_TEAMPROJECTID.
    - AuthorizationHeader: The authorization header. If not provided, it uses the value of the environment variable SYSTEM_ACCESSTOKEN.
&lt;/span&gt;&lt;span class="cs"&gt;.PARAMETER&lt;/span&gt;&lt;span class="cm"&gt; BuildId
    The ID of the build.
&lt;/span&gt;&lt;span class="cs"&gt;.PARAMETER&lt;/span&gt;&lt;span class="cm"&gt; CollectionUri
    The URI of the collection.
&lt;/span&gt;&lt;span class="cs"&gt;.PARAMETER&lt;/span&gt;&lt;span class="cm"&gt; ProjectId
    The ID of the project.
&lt;/span&gt;&lt;span class="cs"&gt;.PARAMETER&lt;/span&gt;&lt;span class="cm"&gt; AuthorizationHeader
    The authorization header.
&lt;/span&gt;&lt;span class="cs"&gt;.OUTPUTS&lt;/span&gt;&lt;span class="cm"&gt;
    Returns a PSCustomObject with the following properties:
    - DistinctCommits: An array of distinct commits.
    - AssociatedWorkItems: An array of associated work items.
&lt;/span&gt;&lt;span class="cs"&gt;.EXAMPLE&lt;/span&gt;&lt;span class="cm"&gt;
    Get-BuildPipelineScopedChanges -BuildId 12345 -CollectionUri "https://dev.azure.com/myorg/" -ProjectId "be77c668-1e13-4360-aa08-264b1d5f64c6" -AuthorizationHeader "Bearer &amp;lt;access_token&amp;gt;"
    Retrieves the distinct commits and associated work items for the build with ID 12345 in the project "be77c668-1e13-4360-aa08-264b1d5f64c6" in the Azure DevOps organization "myorg" using the specified access token.
#&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;CmdletBinding&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$BuildId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;BUILD_BUILDID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Uri&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$CollectionUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SYSTEM_COLLECTIONURI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Guid&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$ProjectId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SYSTEM_TEAMPROJECTID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;()]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$AuthorizationHeader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;ENV&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;SYSTEM_ACCESSTOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;EnsureModuleInstalled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="nv"&gt;$module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-InstalledModule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;$null&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$module&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;  
        &lt;/span&gt;&lt;span class="n"&gt;Install-Module&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CurrentUser&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AllowClobber&lt;/span&gt;&lt;span class="w"&gt;  
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;  
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;EnsureModuleInstalled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"powershell-yaml"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Authorization&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AuthorizationHeader&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$BuildUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CollectionUri$ProjectId&lt;/span&gt;&lt;span class="s2"&gt;/_apis/build/builds/&lt;/span&gt;&lt;span class="nv"&gt;$BuildId&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ChangesUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CollectionUri$ProjectId&lt;/span&gt;&lt;span class="s2"&gt;/_apis/build/builds/&lt;/span&gt;&lt;span class="nv"&gt;$BuildId&lt;/span&gt;&lt;span class="s2"&gt;/changes"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$Build&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$BuildUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Headers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$RepositoryId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;repository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$CommitId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sourceVersion&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$BuildDefinitionId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$BuildDefinitionRevision&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Build&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;definition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;revision&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$BuildDefinitionUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CollectionUri$ProjectId&lt;/span&gt;&lt;span class="s2"&gt;/_apis/build/definitions/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$BuildDefinitionId&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;?revision=&lt;/span&gt;&lt;span class="nv"&gt;$BuildDefinitionRevision&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$BuildDefinition&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$BuildDefinitionUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Headers&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$YamlFilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$BuildDefinition&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;yamlFilename&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$YamlFileUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CollectionUri$ProjectId&lt;/span&gt;&lt;span class="s2"&gt;/_apis/git/repositories/&lt;/span&gt;&lt;span class="nv"&gt;$RepositoryId&lt;/span&gt;&lt;span class="s2"&gt;/items?path=&lt;/span&gt;&lt;span class="nv"&gt;$YamlFilePath&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;download=true&amp;amp;versionDescriptor.version=&lt;/span&gt;&lt;span class="nv"&gt;$CommitId&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;versionDescriptor.versionType=commit"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$YamlFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$YamlFileUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-Yaml&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;span class="nv"&gt;$TriggerPaths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$YamlFile&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trigger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;include&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-replace&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"\*"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$IsMonoRepo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TriggerPaths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TriggerPaths&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ne&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="nv"&gt;$Changes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ChangesUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Headers&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$LatestChangeTimeStamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Changes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$OldestChangeTimeStamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Changes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;timestamp&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$CommitsFilteredByTriggerPaths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$BaseCommitsUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CollectionUri$ProjectId&lt;/span&gt;&lt;span class="s2"&gt;/_apis/git/repositories/&lt;/span&gt;&lt;span class="nv"&gt;$RepositoryId&lt;/span&gt;&lt;span class="s2"&gt;/commits?searchCriteria.fromDate=&lt;/span&gt;&lt;span class="nv"&gt;$OldestChangeTimeStamp&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;searchCriteria.toDate=&lt;/span&gt;&lt;span class="nv"&gt;$LatestChangeTimeStamp&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;searchCriteria.includeWorkItems=true"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$IsMonoRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TriggerPaths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$CommitsUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BaseCommitsUri&lt;/span&gt;&lt;span class="s2"&gt;&amp;amp;searchCriteria.itemPath=&lt;/span&gt;&lt;span class="nv"&gt;$Path&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$Commits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$CommitsUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Headers&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$CommitsFilteredByTriggerPaths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Commits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$Commits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$BaseCommitsUri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Headers&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$CommitsFilteredByTriggerPaths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Commits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$DistinctCommits&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="nv"&gt;$CommitsFilteredByTriggerPaths&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;Group-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-Property&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;commitId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Group&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$DistinctWorkItemUrls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$DistinctCommits&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;workItems&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Unique&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$AssociatedWorkItems&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="nv"&gt;$DistinctWorkItemUrls&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ForEach-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Headers&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;PSCustomObject&lt;/span&gt;&lt;span class="p"&gt;]@{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;DistinctCommits&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$DistinctCommits&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;AssociatedWorkItems&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$AssociatedWorkItems&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

    &lt;p&gt;&lt;a href="https://tiberriver256.github.io/devops/azure-devops-pipelines-related-work-items-monorepo/"&gt;Getting the related work items for an Azure Pipeline run in a monorepo&lt;/a&gt; was originally published by Micah Rairdon at &lt;a href="https://tiberriver256.github.io"&gt;Tiberriver256&lt;/a&gt; on August 28, 2024.&lt;/p&gt;</description><author>Tiberriver256</author><pubDate>Wed, 28 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://tiberriver256.github.io/devops/azure-devops-pipelines-related-work-items-monorepo/</guid></item><item><title>Reduce Cognitive Load</title><link>https://maxleiter.com/blog/reduce-cognitive-load</link><description>&lt;p&gt;The AI team at Vercel has been growing; what was five people a year ago is almost 15.&lt;br /&gt;In most ways, this has been fantastic. But it's been a challenge, too. More people are reading my code, and I'm reading&lt;br /&gt;theirs. &lt;strong&gt;A lot&lt;/strong&gt; of time can be spent just trying to understand what's going on, even if it's a code base you're familiar with, to no fault of the author.&lt;/p&gt;
&lt;p&gt;I've been thinking a lot about how to make this easier,&lt;br /&gt;and it really comes down to focusing on &lt;strong&gt;reducing &lt;a href="https://en.wikipedia.org/wiki/Cognitive_load" rel="noopener noreferrer" target="_blank"&gt;cognitive load&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Obviously, this comes into play all over the place, like when designing APIs and user experiences.&lt;br /&gt;But I've been thinking about it in terms of code. How can I make my code easier to read and understand?&lt;/p&gt;
&lt;p&gt;Here are a few things I've been trying, from the big to the small.&lt;/p&gt;
&lt;h2&gt;1. Incremental PRs&lt;/h2&gt;
&lt;p&gt;When I work on a large feature, I'll often break it up into multiple PRs.&lt;br /&gt;Sometimes, this happens naturally while I'm working and the branches practically create themselves.&lt;br /&gt;Other times, I write the entire feature in one go, but then do my own personal PR review and group the changes into logical chunks.&lt;/p&gt;
&lt;p&gt;If there are no logical chunks, I try to split them into PRs based on impact. I ask myself, &lt;em&gt;can some of this code ship without any risk to production?&lt;/em&gt;&lt;br /&gt;This has its risks regarding issues like stale code if the rest doesn't ship soon after, but I think it's worth it for the reviewer.&lt;/p&gt;
&lt;p&gt;Even if the PR they're reviewing is only a portion of the big picture, your PR description and chunking needs to be enough for them to go off of.&lt;/p&gt;
&lt;h2&gt;2. Coding style&lt;/h2&gt;
&lt;p&gt;I've adjusted a lot of my coding style in the past few years. Any big numbers in JS? Each set of three digits gets an underscore.&lt;/p&gt;
&lt;p&gt;Which would you rather read in a codebase, even if for a second?&lt;/p&gt;
&lt;p&gt;This:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;export const TIMEOUT_TIME = 1000000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or this?&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-js"&gt;export const TIMEOUT_TIME = 100_000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Could you tell there was a missing zero in the second example?&lt;/p&gt;
&lt;h2&gt;3. Comments&lt;/h2&gt;
&lt;p&gt;Your code can be the most beautiful thing in the world. I still don't want to have to read it all&lt;br /&gt;character by character to understand what it does or &lt;em&gt;why&lt;/em&gt; it does it.&lt;/p&gt;
&lt;p&gt;I like to leave comments at the top of my files (which are often named after the feature they're implementing) explaining the high-level overview of the file.&lt;/p&gt;</description><author>Max Leiter</author><pubDate>Wed, 28 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://maxleiter.com/blog/reduce-cognitive-load</guid></item><item><title>Ouroboros DB Dev Journal: Erasure Coding</title><link>https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/</link><description>&lt;p&gt;
      &lt;em&gt;Best viewed on the &lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/"&gt;original page&lt;/a&gt;, where extended functionality like the
    footnote helper is available.&lt;/em&gt;
    &lt;/p&gt;&lt;p&gt;This is a Dev Journal for the Ouroboros DB Project.&lt;br /&gt;
I try to write down my thoughts and ideas Somewhat structured to have it as a reference for later and i publish it to give this information a chance to help other and to get feedback from the community.&lt;br /&gt;
If you have feedback you can write me on my &lt;a href="https://mastodon.social/@heidenstedt"&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Pls note that this is only a Dev Journal and not a Blog Post, so it may be a bit unstructured and not as polished as a Blog Post, including typos and other errors.&lt;/p&gt;
&lt;h2 id="tldr"&gt;&lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/#tldr"&gt;TL;DR:&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Today i worked on a refactoring of the architecture and feasibility of erasure coding for &lt;a href="https://github.com/i5heu/ouroboros-db"&gt;Ouroboros DB&lt;/a&gt;. There are some concerns i had regarding the potential index size and overhead that will result from it, and as it turns out, it is not as bad as i thought but i need a DHT for the index.&lt;/p&gt;
&lt;h2 id="architecture"&gt;&lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/#architecture"&gt;Architecture&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I think it would be super handy to be able to run parts of data pipelines (eg. storing files into chunked,compressed, encrypted and then erasure coded blocks) in a distributed manner. for safety and because i want to be able to run trustless nodes that can&amp;rsquo;t see the raw data, it would be necessary to have the file chunking, compression and encryption part on the server the client currently speaks too, although it would be possible to pre-chunk the file on the client and upload it to different nodes, although here is the question if it is easy to port the chunking algorithm to browser JS or WASM.&lt;/p&gt;
&lt;h3 id="modules"&gt;&lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/#modules"&gt;Modules&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;Refactoring of the code is needed to implement &lt;code&gt;erasure coding&lt;/code&gt;, maybe we can even get rid of the &lt;code&gt;chunk&lt;/code&gt; entirely as a stored thing since we have the data in the &lt;code&gt;parity block&lt;/code&gt;s already.&lt;/p&gt;
&lt;p&gt;Maybe best to add the &lt;code&gt;erasure coding&lt;/code&gt; to the &lt;code&gt;StoreDataPipeline&lt;/code&gt; and add the needed &lt;code&gt;erasure coding&lt;/code&gt; metadata to &lt;code&gt;ChunkData&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;ASCII Art of the new architecture (click to get the .txt file):&lt;/p&gt;
&lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/architecture.txt" target="_blank"&gt;
&lt;p&gt;&lt;div class="imageLoadingWrap"&gt;&lt;img alt="ASCII Art of the new architecture" height="871" src="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/Architecture_hu8ec97601451ce298d71e413e7198ac49_68065_720x0_resize_q85_h2_lanczos_3.webp" title="ASCII Art of the new architecture" width="3030" /&gt;&lt;div class="imageLoading"&gt;&lt;/div&gt;
&lt;/div&gt;&lt;/p&gt;
&lt;/a&gt;
&lt;h2 id="erasure-coding"&gt;&lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/#erasure-coding"&gt;Erasure Coding&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;For now i think it is okay yo make the erasure coding a simple n6 k3, which would result in a &lt;code&gt;parity block&lt;/code&gt; size of 327,680 Bytes or 0.31MB as average. This would result in about 1.875MB per chunk that is 1.25MB big in average. This would result in a 50% overhead for the erasure coding, which i think is quite okay.&lt;/p&gt;
&lt;p&gt;For the goal of storing 100TB in it, which are about 400M Chunks, we would need 2,400,000,000 &lt;code&gt;parity block&lt;/code&gt;s, aka 2,4 Billion.&lt;/p&gt;
&lt;p&gt;If we consider following overhead for the erasure coding:&lt;/p&gt;
&lt;details&gt;
  Parity Meta Data
    This is the Meta Data for a `parity block`:
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-json"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;"parityHash"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"aeae379a6e857728e44164267fdb7a0e27b205d757cc19899586c89dbb221930f1813d02ff93a661859bc17065eac4d6edf3c38a034e6283a84754d52917e5b0"&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;"chunkHash"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"aeae379a6e857728e44164267fdb7a0e27b205d757cc19899586c89dbb221930f1813d02ff93a661859bc17065eac4d6edf3c38a034e6283a84754d52917e5b0"&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;"sizeByte"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;433000&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;"lastChecked"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;"reblanceLog"&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;"userLog"&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"time"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #f92672;"&gt;"from"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;],
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #f92672;"&gt;"this is for the KV Key"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"aeae379a6e857728e44164267fdb7a0e27b205d757cc19899586c89dbb221930f1813d02ff93a661859bc17065eac4d6edf3c38a034e6283a84754d52917e5b0"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/details&gt;
&lt;p&gt;We see that &lt;code&gt;parity block metadata&lt;/code&gt; has under 2373 Bytes of overhead, which is about 0.72%% of the &lt;code&gt;parity block&lt;/code&gt; size (very good). If one node would need to store the entire hash table we look at a size of 5.18TB, which means that we need a DHT for this to work. This also is the case for the Events, which introduce more storage overhead (more in &lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/#dht"&gt;DHT&lt;/a&gt; )&lt;/p&gt;
&lt;h3 id="dht"&gt;&lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/#dht"&gt;DHT&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;We need additional DHT Metadata like this:&lt;/p&gt;
&lt;details&gt;
  DHT Meta Data
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-json"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #f92672;"&gt;"parityHash"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"aeae379a6e857728e44164267fdb7a0e27b205d757cc19899586c89dbb221930f1813d02ff93a661859bc17065eac4d6edf3c38a034e6283a84754d52917e5b0"&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #f92672;"&gt;"storingNodes"&lt;/span&gt;: [
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #f92672;"&gt;"node"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #f92672;"&gt;"lastValidated"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #f92672;"&gt;"node"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #f92672;"&gt;"lastValidated"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #f92672;"&gt;"node"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #f92672;"&gt;"lastValidated"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #f92672;"&gt;"node"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #f92672;"&gt;"lastValidated"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #f92672;"&gt;"node"&lt;/span&gt;: &lt;span style="color: #e6db74;"&gt;"2deb000b57bfac9d72c14d4ed967b572"&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;      &lt;span style="color: #f92672;"&gt;"lastValidated"&lt;/span&gt;: &lt;span style="color: #ae81ff;"&gt;1724760126282&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  ]
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/details&gt;
&lt;p&gt;This example meta data has a size of 674 Bytes, which is about 0.21% of the &lt;code&gt;parity block&lt;/code&gt; size. The &lt;code&gt;DHT meta data&lt;/code&gt; would require an additional 1.47TB of storage.&lt;/p&gt;
&lt;h3 id="overhead"&gt;&lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/#overhead"&gt;Overhead&lt;/a&gt;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;DHT meta data&lt;/code&gt; in combination with the &lt;code&gt;parity block meta data&lt;/code&gt; would have a total overhead of 0.93%% of the &lt;code&gt;erasure coding metadata&lt;/code&gt; relative to the &lt;code&gt;erasure coding parity block size&lt;/code&gt;, the entire metadata overhead at 100TB and 2,4 Billion &lt;code&gt;parity block&lt;/code&gt;s would be 6.65TB.&lt;/p&gt;
&lt;p&gt;Adding this to the overhead from the &lt;code&gt;erasure coding&lt;/code&gt; which are 50% we would have a total overhead of 56.65% relative to the raw data size. If we have a utilization of each &lt;code&gt;chunk&lt;/code&gt; of 1310720 Bits which has been achieved with 10MB files with random binary data. This would result in a HDD to raw data ratio of 43.35% (which is pretty good for a k6n3 config and having self healing capabilities) for following configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;100TB of raw data&lt;/li&gt;
&lt;li&gt;data deduplication via BuzzHash into chunks&lt;/li&gt;
&lt;li&gt;chunk compression via Zstd&lt;/li&gt;
&lt;li&gt;chunk encryption via AES256&lt;/li&gt;
&lt;li&gt;each chunk becomes 6 parity blocks of which 3 can be lost without data loss&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;&lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;I have written a Google Calc Spreadsheet for it, which you can find here: &lt;a href="https://docs.google.com/spreadsheets/d/12Ad4vvA0dLSOffDLz6gMkkIJkYNZE70gY0wI1Qqkg8c/edit?usp=sharing"&gt;ouroboros-db Overhead Calculator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Though this Spreadsheet is neat, i discoverd that i need some surface plotting to find the best configuration for the erasure coding.&lt;br /&gt;
Sadly there is no way to do this in Google Calc, so i will need to write a small Browser App for it&amp;hellip; maybe i use Svelte for it, i haven&amp;rsquo;t used it in a while now.&lt;/p&gt;
&lt;h2 id="further-reading"&gt;&lt;a href="https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/#further-reading"&gt;Further Reading&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;A good overview over what erasure coding is about: &lt;a href="https://transactional.blog/blog/2024-erasure-coding"&gt;Erasure Coding for Distributed Systems&lt;/a&gt;&lt;br /&gt;
Also See &lt;a href="https://news.ycombinator.com/item?id=41361281"&gt;HN comments&lt;/a&gt;&lt;/p&gt;</description><author>Mia Heidenstedt</author><pubDate>Tue, 27 Aug 2024 18:55:05 GMT</pubDate><guid isPermaLink="true">https://heidenstedt.org/posts/2024/ouroboros-db-dev-journal-erasure-coding/</guid></item><item><title>Deploy containers Azure App Services using user-assigned managed identity</title><link>https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/</link><description>&lt;p&gt;This blog post describes my approach to successfully deploying a custom container image to Azure App Services from a private container registry, using a user-assigned managed identity and the Azure CLI. This blog post will cover how to do this using a user-assigned managed identity, and a separate post will cover how to do this [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/"&gt;Deploy containers Azure App Services using user-assigned managed identity&lt;/a&gt; appeared first on &lt;a href="https://nestenius.se"&gt;Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development&lt;/a&gt;.&lt;/p&gt;</description><author>Personal Blog of Tore Nestenius | Insights on .NET, C#, and Software Development</author><pubDate>Tue, 27 Aug 2024 14:56:38 GMT</pubDate><guid isPermaLink="true">https://nestenius.se/azure/deploy-a-container-to-azure-app-services-using-azure-cli-and-user-assigned-managed-identity/</guid></item><item><title>I forgot to use SQL transactions</title><link>https://studiofreya.org/2024/08/i-forgot-to-use-sql-transactions/</link><description>&lt;p&gt;I forgot to use transactions while adding many, but small-ish records to a SQLite database.&lt;/p&gt;
&lt;p&gt;The tests were slowing down at a specific step, enough so I had lots of time to get the process-id (PID) of the test program and attach &lt;code&gt;gdb&lt;/code&gt; to it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;ps ux | grep testprogram
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;gdb /path/to/testprogram &lt;span style="color: #ae81ff;"&gt;1234&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Backtrace:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;(gdb) bt
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;#0  0x00007f2673967694 in fdatasync () from /lib64/libc.so.6
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;#1  0x00007f26745740b5 in unixSync () from /usr/lib64/libsqlite3.so
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;#2  0x00007f26745cb0fd in pagerWalFrames () from /usr/lib64/libsqlite3.so
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;#3  0x00007f26745d7376 in sqlite3PagerCommitPhaseOne.part.0 () from /usr/lib64/libsqlite3.so
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;#4  0x00007f26745d8a0b in sqlite3BtreeCommitPhaseOne.part.0 () from /usr/lib64/libsqlite3.so
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;#5  0x00007f26745db157 in sqlite3VdbeHalt () from /usr/lib64/libsqlite3.so
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;#6  0x00007f2674618322 in sqlite3VdbeExec () from /usr/lib64/libsqlite3.so
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;#7  0x00007f2674619546 in sqlite3_step () from /usr/lib64/libsqlite3.so
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;#8  0x00007f26743e757c in SQLite::Statement::tryExecuteStep() ()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After a couple of times of attaching gdb, and it was at the same step every time, my first thought was that the disks were failing. However, there was no sign of disk errors in the system logs, and SQLite is very fast in the average case.&lt;/p&gt;
&lt;p&gt;For this project I&amp;rsquo;m using SQLiteCpp, as you can see from frame #8. It wraps the C interface in a C++-RAII interface.&lt;/p&gt;
&lt;p&gt;And then it dawned upon me.&lt;/p&gt;
&lt;p&gt;Transactions.&lt;/p&gt;
&lt;p&gt;When you know where and when, the fix is trivial.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-cpp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Dynamically adjust insert statement to number of dimensions
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;const&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;auto&lt;/span&gt; insert_sql &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; insert_statement_sql(num_scenarios);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Prepare statement
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;SQLite&lt;span style="color: #f92672;"&gt;::&lt;/span&gt;Statement query(db, insert_sql);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Add transaction
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;SQLite&lt;span style="color: #f92672;"&gt;::&lt;/span&gt;Transaction transact(db);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Add data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;for&lt;/span&gt; (&lt;span style="color: #66d9ef;"&gt;auto&lt;/span&gt; n &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;; n &lt;span style="color: #f92672;"&gt;&amp;lt;&lt;/span&gt; num_timestamps; &lt;span style="color: #f92672;"&gt;++&lt;/span&gt;n)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;	&lt;span style="color: #75715e;"&gt;// Insert data, check for errors, reset query
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;// Commit
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;transact.commit();
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Speedup was instant. From taking about 60 seconds, the test takes about no time at all.&lt;/p&gt;
&lt;p&gt;Take home:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Have tests.&lt;/li&gt;
&lt;li&gt;Have multiple test.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s your code, not the library / compiler / disk / CPU / network.&lt;/li&gt;
&lt;li&gt;If it&amp;rsquo;s the network, it&amp;rsquo;s always DNS&lt;/li&gt;
&lt;/ol&gt;</description><author>Studiofreya SSG Site</author><pubDate>Tue, 27 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/2024/08/i-forgot-to-use-sql-transactions/</guid></item><item><title>💻 Building Beautiful Admin Dashboards in Phoenix with Backpex</title><link>https://james-carr.org/posts/2024-08-27-phoenix-admin-with-backpex/</link><description>Something that I noticed as I have begun building out applications in Phoenix is the complete lack of any kind of &amp;ldquo;Admin View.&amp;rdquo; Most frameworks like this don&amp;rsquo;t come with one included, but I guess I have just been spoiled by Django&amp;rsquo;s inclusion of these by default. I searched around and found several do exist and will maybe spend some time looking at them in the future, but today I&amp;rsquo;ll share my experience integrating Backpex.</description><author>James Carr</author><pubDate>Tue, 27 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://james-carr.org/posts/2024-08-27-phoenix-admin-with-backpex/</guid></item><item><title>Iterate Through Strings in Go with a for-range Loop</title><link>https://nelson.cloud/iterate-through-strings-in-go-with-a-for-range-loop/?ref=rss</link><description>You can use for-range loops to iterate through strings in Go without splitting because Go handles strings as byte slices.</description><author>Nelson Figueroa</author><pubDate>Tue, 27 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://nelson.cloud/iterate-through-strings-in-go-with-a-for-range-loop/?ref=rss</guid></item><item><title>Rebuilding My iTerm Setup In Wezterm</title><link>https://www.danielcorin.com/til/wezterm/rebuilding-my-iterm-setup-in-wezterm/</link><description>Rebuilding My iTerm Setup In Wezterm</description><author>Thought Eddies</author><pubDate>Mon, 26 Aug 2024 22:20:54 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/wezterm/rebuilding-my-iterm-setup-in-wezterm/</guid></item><item><title>When does electrifying a widget actually reduce emissions?</title><link>https://pehrlich.substack.com/p/when-does-electrifying-a-widget-actually</link><description>Is it better to wait?</description><author>Into the Details</author><pubDate>Mon, 26 Aug 2024 17:06:44 GMT</pubDate><guid isPermaLink="true">https://pehrlich.substack.com/p/when-does-electrifying-a-widget-actually</guid></item><item><title>writing blog posts - done is better than perfect</title><link>https://evilcookie.de/writing-blog-posts---done-is-better-than-perfect.html</link><description/><author>blog</author><pubDate>Mon, 26 Aug 2024 13:19:41 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/writing-blog-posts---done-is-better-than-perfect.html</guid></item><item><title>Lathkill Dale Valley Loop (Part 2): Ruins, Mines and Over Haddon</title><link>https://sam.hooke.me/trip/2024/08/lathkill-dale-valley-loop-part-2/</link><description>&lt;h2 id="ruins"&gt;Ruins&lt;/h2&gt;
&lt;p&gt;The remains of past industry are scattered throughout the valley. At some point, we passed this abandoned grindstone:&lt;/p&gt;


&lt;figure class="single"&gt;
 &lt;div class="single-inset"&gt;
 










 
 
 
 &lt;a href="https://sam.hooke.me/images/trip/2024/lathkill-dale/2938.jpg"&gt;
 
 &lt;img alt="A large grindstone resting against a tree, half buried in the dirt." src="https://sam.hooke.me/images/trip/2024/lathkill-dale/2938_hu3642502377703434257.jpg" /&gt;
 
 &lt;/a&gt;
 


 &lt;/div&gt;
&lt;/figure&gt;
&lt;p&gt;The river bends left, then as it bends right again there is a small clearing. Several stone brick pillars remain standing, overgrown, on both sides of the river. Presumably they used to form some sort of bridge that has long since collapsed. Here is one such pillar:&lt;/p&gt;</description><author>Sam Hooke</author><pubDate>Mon, 26 Aug 2024 12:15:01 GMT</pubDate><guid isPermaLink="true">https://sam.hooke.me/trip/2024/08/lathkill-dale-valley-loop-part-2/</guid></item><item><title>Lathkill Dale Valley Loop (Part 1): Fields, Valleys and Waterfalls</title><link>https://sam.hooke.me/trip/2024/08/lathkill-dale-valley-loop-part-1/</link><description>&lt;div id="map-main"&gt;&lt;/div&gt;




&lt;p&gt;Despite the title of this trip, there is no single route called the &lt;em&gt;Lathkill Dale Valley Loop&lt;/em&gt;. There are countless paths that weave through the valley, so if you search for routes, you&amp;rsquo;ll find numerous possibilities of varying lengths. Some are loops, others are there-and-back again. Some start at Over Haddon, or Monyash.&lt;/p&gt;
&lt;p&gt;My goal was to take a route that was a loop, in the 5 to 10 mile range, and avoided roads as much as possible. I was also keen to see the cave, waterfall, ruins and mines. This route does all that, and was based upon &lt;a href="https://www.walkinderbyshire.co.uk/lovely-lathkill-dale/"&gt;these instructions&lt;/a&gt;, with the minor addition of a spur to visit the Lathkill Head Cave.&lt;/p&gt;</description><author>Sam Hooke</author><pubDate>Mon, 26 Aug 2024 12:15:00 GMT</pubDate><guid isPermaLink="true">https://sam.hooke.me/trip/2024/08/lathkill-dale-valley-loop-part-1/</guid></item><item><title>Leader Election With S3 Conditional Writes</title><link>https://www.morling.dev/blog/leader-election-with-s3-conditional-writes/</link><description>&lt;div class="toc" id="toc"&gt;
&lt;div id="toctitle"&gt;Table of Contents&lt;/div&gt;
&lt;ul class="sectlevel1"&gt;
&lt;li&gt;&lt;a href="#_the_algorithm"&gt;The Algorithm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_obtaining_the_lock"&gt;Obtaining the Lock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_expiring_a_lock"&gt;Expiring a Lock&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_lock_validity"&gt;Lock Validity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="#_fencing_off_zombies"&gt;Fencing Off Zombies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="paragraph"&gt;
&lt;p&gt;In distributed systems, for instance when scaling out some workload to multiple compute nodes,
it is a common requirement to select a &lt;em&gt;leader&lt;/em&gt; for performing a given task:
only one of the nodes should process the records from a Kafka topic partition, write to a file system, call a remote API, etc.
Otherwise, multiple workers may end up doing the same task twice, overwriting each other’s data, and worse.&lt;/p&gt;
&lt;/div&gt;</description><author>Gunnar Morling</author><pubDate>Mon, 26 Aug 2024 11:15:00 GMT</pubDate><guid isPermaLink="true">https://www.morling.dev/blog/leader-election-with-s3-conditional-writes/</guid></item><item><title>2024-08-26-001</title><link>https://srijan.ch/notes/2024-08-26-001</link><description>Note to followers of my site using RSS feeds - I've removed the microblog replies/likes etc kind of posts from the "All Posts" feed. I feel social interaction posts like that should not be part of the default feed of my website. There is always the notes feed that includes all microblog posts including reactions / interactions. A list of feeds available can be found here: https://srijan.ch/feed/ …</description><author>Srijan Choudhary, all posts</author><pubDate>Mon, 26 Aug 2024 10:25:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2024-08-26-001</guid></item><item><title>Why is Kubernetes also called k8s</title><link>https://nitinnain.com/why-is-kubernetes-also-called-k8s/</link><description>&amp;#8220;K8s&amp;#8221; has 8 characters between k and n. K8s is simply a (cool!) way to shorten it&amp;#8217;s length. There are other places where you might have noticed this style of abbreviation as well: I saw it first in &amp;#8220;i18n&amp;#8221; for &amp;#8220;internationalization&amp;#8221; (from where it seems to have started). There&amp;#8217;s also l10n for &amp;#8220;localization&amp;#8221;. &amp;#8211; n7n</description><author>Nitin Nain</author><pubDate>Mon, 26 Aug 2024 10:04:31 GMT</pubDate><guid isPermaLink="true">https://nitinnain.com/why-is-kubernetes-also-called-k8s/</guid></item><item><title>Media Ownership</title><link>https://siddhesh.substack.com/p/media-ownership</link><description>Or lack thereof</description><author>Obvious Bicycle</author><pubDate>Mon, 26 Aug 2024 04:33:29 GMT</pubDate><guid isPermaLink="true">https://siddhesh.substack.com/p/media-ownership</guid></item><item><title>Price Gouging</title><link>https://robkohr.com/articles/price-gouging</link><description>Price Gouging</description><author>RobKohr's Blog</author><pubDate>Mon, 26 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://robkohr.com/articles/price-gouging</guid></item><item><title>Cursor review: Changing the way I create software</title><link>https://zackproser.com/blog/cursor-review</link><description>I have been experimenting with AI assisted dev tools nonstop. Cursor probably had the biggest impact of all, so far.</description><author>Zachary Proser</author><pubDate>Mon, 26 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/cursor-review</guid></item><item><title>Announcing Code Hike 1.0</title><link>https://codehike.org/blog/v1</link><description>A new approach to turn markdown into rich interactive experiences</description><author>Rodrigo Pombo</author><pubDate>Mon, 26 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://codehike.org/blog/v1</guid></item><item><title>Using search as a primary datastore since the docs said not to</title><link>https://ntietz.com/blog/the-docs-said-no-search-primary/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Look, I'm sorry, but if the docs say not to do something that's like &lt;em&gt;catnip&lt;/em&gt;.
Then I just &lt;em&gt;have&lt;/em&gt; to do it.
So when I saw that the &lt;a href="https://shortclick.link/nsx6rt" rel="nofollow"&gt;Typesense&lt;/a&gt; docs say &lt;a href="https://typesense.org/docs/overview/use-cases.html#bad-use-cases" rel="nofollow"&gt;not to use it&lt;/a&gt; as a primary datastore?
Well well well, that's what we'll have to do.&lt;/p&gt;
&lt;p&gt;I spent a little bit of time figuring out what a bad &lt;em&gt;but plausible&lt;/em&gt; use-case would be.
The answer is: a chat app.
Most chat apps have a search feature, so if you use search for the primary datastore, you get to remove another component!&lt;/p&gt;
&lt;p&gt;Note: this is a sponsored post.
I was paid by Typesense to write this post.
The brief was to use Typesense in a small project and write about it, the good and the bad&lt;sup class="footnote-reference" id="fr-brief-1"&gt;&lt;a href="https://ntietz.com/blog/the-docs-said-no-search-primary/#fn-brief"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
They have not reviewed this post before publication.&lt;/p&gt;
&lt;h1 id="what-does-the-chat-app-look-like"&gt;What does the chat app look like?&lt;/h1&gt;
&lt;p&gt;One of life's hard problems is naming things.
This chat app, like all Super Serious Side Projects, needs a fitting name, and so I arrived at: Taut.
It's named such because it is chat, but it sure ain't &lt;em&gt;Slack&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The build-out was pretty straightforward and you can see the &lt;a href="https://github.com/ntietz/taut-chat"&gt;repo on GitHub&lt;/a&gt;.
It's licensed under the AGPL, and you should almost certainly &lt;em&gt;not&lt;/em&gt; reuse this code—I'm doing what you're not supposed to!
But it's open-source, so feel free to draw inspiration from it or use it as an example of how to use the Go SDK for Typesense.&lt;/p&gt;
&lt;p&gt;Amusingly, the repo stats show that CSS is what I have the most of.
The Go backend for this is pretty simple, and the JS is non-existent since I used htmx.
Most of that CSS is not hand-written, though, since I used Tailwind.&lt;/p&gt;
&lt;p&gt;Here's what the finished app looks like.
We have a login screen, which has no password requirement because this is for trustworthy people only!
You enter your handle and then you're logged in.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/the-docs-said-no-search-primary/&amp;#x2F;processed_images&amp;#x2F;login.d37229b7affa7486.png" /&gt;
&lt;p&gt;Once you're logged in, you see a chat interface!
Here's a chat between two of our characters, Nicole and Maddie.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/the-docs-said-no-search-primary/&amp;#x2F;processed_images&amp;#x2F;chat-1.4f299b15c9821a5f.png" /&gt;
&lt;p&gt;And here's another, between Nicole and Amy, who are apparently coworkers.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/the-docs-said-no-search-primary/&amp;#x2F;processed_images&amp;#x2F;chat-2.df555dbe83d9dbde.png" /&gt;
&lt;p&gt;Oops, it looks like Nicole is going to put corporate details into this chat app!
I guess we'd better look at how it's implemented to see if that's okay.&lt;/p&gt;
&lt;h1 id="modeling-our-data"&gt;Modeling our data&lt;/h1&gt;
&lt;p&gt;The first thing we need for our web app is a data model.
For our chat app, we really need two main things: users and messages.
Each user should have a handle, and each message should have who it's from and to as well as what was said.&lt;/p&gt;
&lt;p&gt;I ended up with these models:&lt;/p&gt;
&lt;pre class="language-go " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-go"&gt;&lt;span style="color: #fa5c4b;"&gt;type &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;User &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;struct &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ID      &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;string &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;`json:&amp;quot;id&amp;quot;`
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Handle  &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;string &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;`json:&amp;quot;handle&amp;quot;`
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Credits &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;int64  &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;`json:&amp;quot;credits&amp;quot;`
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;type &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;Message &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;struct &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ID &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;string &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;`json:&amp;quot;id&amp;quot;`
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Sender    &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;string &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;`json:&amp;quot;from_id&amp;quot;`
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Recipient &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;string &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;`json:&amp;quot;to_id&amp;quot;`
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Content   &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;string &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;`json:&amp;quot;content&amp;quot;`
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Timestamp &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;int64  &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;`json:&amp;quot;timestamp&amp;quot;`
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;(Ignore "Credits", sssshhh, we'll come back to that.)&lt;/p&gt;
&lt;p&gt;To get records &lt;em&gt;into&lt;/em&gt; the datastore, we also have to configure our schema.
There are some auto-schema settings available, but I wasn't sure how that worked and I want to be &lt;em&gt;certain&lt;/em&gt; which schema is picked up, so I went with the old trusty to define how my data is laid out.
It's pretty straightforward: you tell it what fields you have and what their types are, and then you're done.
The ID field is created for you automatically, so you can leave that one off.&lt;/p&gt;
&lt;p&gt;Here's an example of creating the users schema.&lt;/p&gt;
&lt;pre class="language-go " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-go"&gt;&lt;span style="color: #fdf4c1;"&gt;ctx &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Background&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;userSchema &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &amp;amp;&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;api&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;CollectionSchema&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;users&amp;quot;&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Fields&lt;/span&gt;&lt;span&gt;: []&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;api&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Field&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;handle&amp;quot;&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;string&amp;quot;&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;span&gt;    {
&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Name&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;credits&amp;quot;&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;      &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Type&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;int64&amp;quot;&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;    },
&lt;/span&gt;&lt;span&gt;  },
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;_&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Collections&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;userSchema&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;!= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;nil &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You'd do something similar for any other collection.
This isn't too bad, but it's a bit redundant with what we already defined in the struct.
There could be an opportunity for some languages to auto-generate this for you, though the &lt;a href="https://github.com/typesense/typesense-go"&gt;&lt;code&gt;typesense-go&lt;/code&gt;&lt;/a&gt; library doesn't.&lt;/p&gt;
&lt;p&gt;Creating records is where we &lt;em&gt;start&lt;/em&gt; to see why what we're doing is probably a bad idea.
I only want to create a record if there isn't a user already.
In relational databases (especially with an ORM), this is a succinct operation.
Here, it gets a little more verbose.&lt;/p&gt;
&lt;p&gt;We retrieve all the existing users by querying by the user's handle.&lt;/p&gt;
&lt;pre class="language-go " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-go"&gt;&lt;span style="color: #fdf4c1;"&gt;ctx &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Background&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;query &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;api&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;SearchCollectionParams&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Q&lt;/span&gt;&lt;span&gt;:       &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;handle&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;QueryBy&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;handle&amp;quot;&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;matchingUsers&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Collection&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;users&amp;quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Documents&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;query&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;!= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;nil &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we count how many there are, and if there is not already a user, we create one!&lt;/p&gt;
&lt;pre class="language-go " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-go"&gt;&lt;span style="color: #fa5c4b;"&gt;if &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;*&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;matchingUsers&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Found&lt;/span&gt;&lt;span&gt;) &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;gt; &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0 &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;nil
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;id &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;handle
&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;user &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;User&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ID&lt;/span&gt;&lt;span&gt;:      &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;id&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Handle&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;handle&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Credits&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;100&lt;/span&gt;&lt;span&gt;,
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;_&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Collection&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;users&amp;quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Documents&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Create&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;user&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;!= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;nil &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;nil
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A natural question may be, why not use an &lt;code&gt;Update&lt;/code&gt; operation, or &lt;code&gt;upsert&lt;/code&gt; if it's available?
I wanted to do something like this, but this will udpate the document we provide if it already exists!
There's no create-if-not-exists that I could find, and I didn't want to reset that &lt;code&gt;Credits&lt;/code&gt; field.&lt;/p&gt;
&lt;p&gt;We do similar for messages, which is in &lt;a href="https://github.com/ntietz/taut-chat/blob/main/pkg/web/models.go"&gt;models.go&lt;/a&gt;.
Now we have our models, and we can create instances of them!&lt;/p&gt;
&lt;h1 id="building-the-views"&gt;Building the views&lt;/h1&gt;
&lt;p&gt;Everything is a single-page app these days and &lt;em&gt;doesn't need to be&lt;/em&gt;, so I built this in a traditional client-server way.
But since it's, you know, &lt;em&gt;chat&lt;/em&gt;, it has to be more interactive.
That's easily addressed with htmx to make things reload!
I did polling here for simplicity, but you can also do it over websockets, which would be the better approach.&lt;/p&gt;
&lt;p&gt;The login view isn't too interesting, but the main chat view and search views are where we see the meat.
Let's look at the chat view first.&lt;/p&gt;
&lt;p&gt;Since we're using htmx, we'll implement &lt;em&gt;fragments&lt;/em&gt; of views, which we'll load to replace specific parts of the page.
This led me to write the views in a modular way, and &lt;em&gt;really&lt;/em&gt; reminded me how good we have it with other template libraries, and how bare-bones Go's built-in &lt;a href="https://pkg.go.dev/html/template"&gt;&lt;code&gt;html/template&lt;/code&gt;&lt;/a&gt; library is.&lt;/p&gt;
&lt;p&gt;The main view looks like this.
Ignoring the html_open and html_close templates, there's not a lot to it.
Just some divs with styles and invoking the templates for our user list and chat window.&lt;/p&gt;
&lt;pre class="language-html " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-html"&gt;&lt;span&gt;{{ template &amp;quot;html_open&amp;quot; }}
&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;main &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;class=&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;w-full h-full&amp;quot;&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;div &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;class=&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;flex flex-col w-full h-full p-4 bg-flagpink&amp;quot;&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;
&lt;/span&gt;&lt;span&gt;{{ template &amp;quot;header&amp;quot; . }}
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;div &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;class=&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;flex flex-row h-full w-full&amp;quot;&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;
&lt;/span&gt;&lt;span&gt;    {{ template &amp;quot;user_list&amp;quot; . }}
&lt;/span&gt;&lt;span&gt;    {{ template &amp;quot;chat_window&amp;quot; . }}
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;div&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;div&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;
&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;main&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;
&lt;/span&gt;&lt;span&gt;{{ template &amp;quot;html_close&amp;quot; }}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Each of those is also pretty simple.
This is how the user list is populated.
Each user has a handle, and clicking on their handle will let you chat with them.&lt;/p&gt;
&lt;pre class="language-html " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-html"&gt;&lt;span&gt;{{ define &amp;quot;user_list&amp;quot; }}
&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;div &lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;id&lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;=&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;users-list&amp;quot; &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;class=&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;bg-white outline outline-4 outline-black h-full p-2 flex flex-col&amp;quot; &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;hx-get=&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;/fragment/users&amp;quot; &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;hx-trigger=&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;every 5s&amp;quot; &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;hx-swap=&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;outerHTML&amp;quot;&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;strong&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;People&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;strong&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;  {{ range .Handles }}
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;a &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;href=&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;/start-chat/{{ . }}&amp;quot;&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;&lt;/span&gt;&lt;span&gt;{{ . }}&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;a&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;
&lt;/span&gt;&lt;span&gt;  {{ end }}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;lt;/&lt;/span&gt;&lt;span style="font-weight: bold; color: #8ec07c;"&gt;div&lt;/span&gt;&lt;span style="color: #83a598;"&gt;&amp;gt;
&lt;/span&gt;&lt;span&gt;{{ end }}
&lt;/span&gt;&lt;span&gt;{{ template &amp;quot;user_list&amp;quot; . }}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On the backend, we have to get data &lt;em&gt;into&lt;/em&gt; these views, though.
To do that, we're off to query Typesense again!
Full details are in &lt;a href="https://github.com/ntietz/taut-chat/blob/main/pkg/web/views.go"&gt;the &lt;code&gt;views.go&lt;/code&gt; file&lt;/a&gt;, here are the highlights.&lt;/p&gt;
&lt;p&gt;The main thing we need to do is list users.
We can make a function to return that list, which will take a Typesense client as an argument (here, it's called &lt;code&gt;h.Ts&lt;/code&gt; due to either idiomatic or poor naming conventions).&lt;/p&gt;
&lt;pre class="language-go " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-go"&gt;&lt;span style="color: #fdf4c1;"&gt;handles&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ListUserHandles&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;h&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Ts&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Implementing this is pretty easy.
We search for all users, retrieve them, then from each record we extract just the handle.&lt;/p&gt;
&lt;pre class="language-go " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-go"&gt;&lt;span style="color: #fa5c4b;"&gt;func &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;ListUserHandles&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ts &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;*&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;typesense&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;Client&lt;/span&gt;&lt;span&gt;) ([]&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;error&lt;/span&gt;&lt;span&gt;) {
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ctx &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;context&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Background&lt;/span&gt;&lt;span&gt;()
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;query &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;api&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;SearchCollectionParams&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;		&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Q&lt;/span&gt;&lt;span&gt;:       &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;*&amp;quot;&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;		&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;QueryBy&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;handle&amp;quot;&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;userRecords&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Collection&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;users&amp;quot;&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Documents&lt;/span&gt;&lt;span&gt;().&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Search&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;ctx&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;query&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;if &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;!= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;nil &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;		&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;nil&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;err
&lt;/span&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;handles &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;make&lt;/span&gt;&lt;span&gt;([]&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;string&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;0&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;for &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;_&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;userRecord &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;range &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;*&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;*&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;userRecords&lt;/span&gt;&lt;span&gt;).&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Hits &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;		&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;handle &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;*&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;userRecord&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Document&lt;/span&gt;&lt;span&gt;)[&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;handle&amp;quot;&lt;/span&gt;&lt;span&gt;].(&lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;string&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;		&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;handles &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #fabd2f;"&gt;append&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;handles&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;handle&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;	&lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;return &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;handles&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;nil
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Not bad, given what we're doing!
It could be shorter, but this is a raw library, so it's not too tough to wrap that up yourself.&lt;/p&gt;
&lt;h1 id="displaying-messages-got-sketchy"&gt;Displaying messages got... sketchy&lt;/h1&gt;
&lt;p&gt;You're going to run into cases like this when you hold something wrong on purpose, but yeah, I really stepped in it with message retrieval.
What we wanted was to display all the chat messages between two users, and what we got was definitely that, but then also maybe something spicy.
To make it work, I definitely misused the query interface.&lt;/p&gt;
&lt;p&gt;When you query Typesense, you get a few parameters.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;q&lt;/code&gt; is the search string. For a recipes app, this might be &lt;code&gt;"pizza"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;query_by&lt;/code&gt; says which field to search for &lt;code&gt;q&lt;/code&gt; within. This could be something like &lt;code&gt;"description"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter_by&lt;/code&gt; lets you provide some criteria to filter out non-matching records. This could be &lt;code&gt;num_steps:&amp;lt;5&lt;/code&gt;, because I want a &lt;em&gt;simple&lt;/em&gt; pizza recipe.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Now, here's what I wound up with to search for messages.
Remember that our messages have a sender, recipient, and content.
Here we're just looking at messages from one user to the other, so we'll just get one side of the conversation.&lt;/p&gt;
&lt;pre class="language-go " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-go"&gt;&lt;span style="color: #fdf4c1;"&gt;filter &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Sprintf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;from_id:=&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;%s&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;from&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;query &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;api&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;SearchCollectionParams&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Q&lt;/span&gt;&lt;span&gt;:        &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;to&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;QueryBy&lt;/span&gt;&lt;span&gt;:  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;to_id&amp;quot;&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;FilterBy&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;filter&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;SortBy&lt;/span&gt;&lt;span&gt;:   &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;timestamp:desc&amp;quot;&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your alarm bells are going off right now, blaring "red alert," yeah, I hear you.
What I did here is a &lt;em&gt;cardinal sin of web development&lt;/em&gt;, one of the &lt;a href="https://owasp.org/www-project-top-ten/"&gt;OWASP Top 10&lt;/a&gt;.
I allowed an injection attack.
It's all because of this line:&lt;/p&gt;
&lt;pre class="language-go " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-go"&gt;&lt;span style="color: #fdf4c1;"&gt;filter &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Sprintf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;from_id:=&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;%s&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;from&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;See, Typesense doesn't have parameterized queries.
Those are standard-issue in SQL and when you use them you're protected from SQL injection attacks.
Here, we don't have them, sooooo...
If we just carefully craft a handle, that can end up doing fun things from inside our filter query.&lt;/p&gt;
&lt;p&gt;I logged in with my &lt;em&gt;totally normal&lt;/em&gt; handle, &lt;code&gt;1||from_id:!=1&lt;/code&gt;, and what do you know...&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/the-docs-said-no-search-primary/&amp;#x2F;processed_images&amp;#x2F;injection-1.5b3c62f77cbfd45c.png" /&gt;
&lt;p&gt;Whoops, now as long as I get my own username into the filter field, I can see anyone else's chats!
With that query above, viewing anyone's chats &lt;em&gt;with me&lt;/em&gt; actually result in showing me &lt;em&gt;any&lt;/em&gt; message which was intended for them.
Now we can see Nicole messaging Amy about those work secrets, oh no!&lt;/p&gt;
&lt;p&gt;To protect against this, you have a few options.
The best solution is to use &lt;a href="https://typesense.org/docs/26.0/api/api-keys.html#generate-scoped-search-key" rel="nofollow"&gt;scoped search keys&lt;/a&gt;.
These let you essentially pre-filter the dataset with a filter that cannot be modified, so even if someone injects into your filter they can't gain access to data they otherwise can't see.
This is a bit more work than parameterized queries would be, though, so I'm a &lt;em&gt;touch&lt;/em&gt; sad that this is the solution and I hope parameterized queries land someday!&lt;/p&gt;
&lt;p&gt;You could also either &lt;em&gt;ban user input from filter fields&lt;/em&gt; or &lt;em&gt;sanitize user input&lt;/em&gt;, but both of these are error prone.
It's very easy to slip up and allow user input through, and it's really tough to make sure the sanitizer is correct.
So it's best not to rely on these and do it with scoped keys!&lt;/p&gt;
&lt;h1 id="searching-messages-was-easy"&gt;Searching messages was easy&lt;/h1&gt;
&lt;p&gt;The star of the show here, really, is searching messages.
This was delightfully easy.
Here's what it looks like.&lt;/p&gt;

&lt;img src="https://ntietz.com/blog/the-docs-said-no-search-primary/&amp;#x2F;processed_images&amp;#x2F;search-1.64228f7e2787fa9a.png" /&gt;

&lt;img src="https://ntietz.com/blog/the-docs-said-no-search-primary/&amp;#x2F;processed_images&amp;#x2F;search-2.bbc4924c749b4938.png" /&gt;
&lt;p&gt;We can see that in this search, we're only seeing Nicole's prviate work chat in her own history!
And otherwise, we get the results we'd expect.&lt;/p&gt;
&lt;p&gt;Typesense helps highlight where the query was found in the search results!
I had a small challenge with it, because it's different than what I'm used to.
In other libraries I've used, I will get back the indexes of where highlighted text starts and ends.
Typesense gives me a convenience here, specifying the start/end tags!
The challenge I ran into is how I would make sure that the underlying content is all escaped, to prevent injection attacks, without also escaping these start/end tags!
I'm sure there's some way to do that, but I wasn't clear on how.&lt;/p&gt;
&lt;p&gt;As far as Taut goes?
I'm just yeeting those messages raw into the finished template, as html.
I'm also definitely &lt;em&gt;not&lt;/em&gt; putting this on the public internet, because y'all can't be trusted like that and someone would 100% immediately do a script injection attack here.&lt;/p&gt;
&lt;p&gt;This is what our search query looks like:&lt;/p&gt;
&lt;pre class="language-go " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-go"&gt;&lt;span style="color: #fdf4c1;"&gt;filter &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;fmt&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Sprintf&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;from_id:=&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;%s&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt; || to_id:=&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;%s&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;currentUser&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;currentUser&lt;/span&gt;&lt;span&gt;)
&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;qparams &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;:= &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;api&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;SearchCollectionParams&lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;Q&lt;/span&gt;&lt;span&gt;:                   &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;query&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;QueryBy&lt;/span&gt;&lt;span&gt;:             &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;FilterBy&lt;/span&gt;&lt;span&gt;:            &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;filter&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;SortBy&lt;/span&gt;&lt;span&gt;:              &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;timestamp:desc&amp;quot;&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;HighlightStartTag&lt;/span&gt;&lt;span&gt;:   &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&amp;lt;b&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;HighlightEndTag&lt;/span&gt;&lt;span&gt;:     &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;&amp;lt;/b&amp;gt;&amp;quot;&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;HighlightFullFields&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;pointer&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;String&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #b8bb26;"&gt;&amp;quot;content&amp;quot;&lt;/span&gt;&lt;span&gt;),
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This one has the same vulnerability as before, but with a twist: instead of showing messages to the attacker, it will show the attacker's messages to &lt;em&gt;you&lt;/em&gt;.
This is delightful when you pair with a &lt;del&gt;2012 Cabernet&lt;/del&gt; script injection attack.
Again, you would mitigate this with scoped search keys, but I didn't, so we've got this little delight.&lt;/p&gt;
&lt;h1 id="why-shouldn-t-you-use-it-as-a-primary-datastore"&gt;Why shouldn't you use it as a primary datastore?&lt;/h1&gt;
&lt;p&gt;So, that's Taut.
I said at the outset that the docs told me not to do this and I did it anyway.
Why do they say not to do it?&lt;/p&gt;
&lt;p&gt;There are a few reasons.
Some of them were highlighted above, but some are things you'd run into if you kept going with this.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Flexibility of queries&lt;/strong&gt; is a big one for me.
Relational databases have SQL, which is designed for the sort of expressive queries that you do in this sort of app!
On the other hand, Typesense is &lt;strong&gt;built for search!&lt;/strong&gt;
So the queries are optimized for &lt;em&gt;search scenarios&lt;/em&gt;, which are not the same.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Lack of parameterized queries&lt;/strong&gt; is another one for me.
I want my primary datastore to be something that's hardened and really trusted, from both a reliability and a security perspective.
Something which doesn't have parameterized queries makes me look twice, from a security perspective.
Maybe we &lt;em&gt;shouldn't&lt;/em&gt; put user input into filter fields but, okay, someone is &lt;em&gt;going&lt;/em&gt; to.
We should make that path something that can be reasonably secured.
The existing solution of scoped search keys is also a reasonable one, but it's one that's not highlighted in the documentation around filters, so again, someone is &lt;em&gt;going to do this&lt;/em&gt; in production.&lt;/p&gt;
&lt;p&gt;If you kept adding features to this app, you'd run into &lt;strong&gt;lack of transactions&lt;/strong&gt;.
Again, this makes total sense for document search!
But for a primary datastore, you often will have multiple things you want to have happen together or not at all.
The &lt;code&gt;Credits&lt;/code&gt; field I'd included?
Originally I wanted to implement a feature that's totally extra, called Extra Chats.
If you make a chat "extra", it would send confetti or something.
To do this, you'd have to send the message &lt;em&gt;and&lt;/em&gt; deduct from a user's credits simultaneously.&lt;/p&gt;
&lt;p&gt;You can't insert/update records across two collections, though, and you can't lock rows!
There are solutions, like using event sourcing, but... those end up pretty complicated.&lt;/p&gt;
&lt;p&gt;And then you also have &lt;strong&gt;data durability&lt;/strong&gt;.
Typesense stores everything in memory, so unless it's configured to write to the disk, you can drop data.
I was a &lt;em&gt;little&lt;/em&gt; annoyed that this is turned on by default, because the wind was taken out of my sails for this point.
Turns out, Typesense has worked a lot into making things reliable and durable!
Writing data to the disk is enabled by default, and you can enable &lt;a href="https://typesense.org/docs/guide/high-availability.html" rel="nofollow"&gt;clustering for high availability&lt;/a&gt; as well.&lt;/p&gt;
&lt;p&gt;Ultimately, though, it's not &lt;em&gt;designed&lt;/em&gt; to be your primary datastore, so you should probably listen to that.
There are going to be things that aren't handled perfectly for durability since that's not what they're designing for.
So probably don't do this for real.&lt;/p&gt;
&lt;h1 id="it-seems-nice-if-you-hold-it-right"&gt;It seems nice, if you hold it right&lt;/h1&gt;
&lt;p&gt;I came away from this project hoping that I have a use case sometime soon to use Typesense the &lt;em&gt;right&lt;/em&gt; way.
There are rough edges, of course, because &lt;em&gt;everthing&lt;/em&gt; has them.
(Seriously, &lt;em&gt;please&lt;/em&gt; add parameterized queries, please please, that seems like a big win for happy path security!)&lt;/p&gt;
&lt;p&gt;For actually &lt;em&gt;searching&lt;/em&gt; across documents?
Oh, it seems really nice!
I'd love to use it in that way and get to see it in its environment where it shines.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-brief"&gt;
&lt;p&gt;Amusingly, the brief actually stipulates that I'd use it &lt;em&gt;as the primary data store&lt;/em&gt;, because I'd pitched that as the idea before the brief was issued and signed.
So they did, technically, pay me to use their product wrong! &lt;a href="https://ntietz.com/blog/the-docs-said-no-search-primary/#fr-brief-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 26 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/the-docs-said-no-search-primary/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Be Seen on your Motorcycle</title><link>https://dheinemann.com/be-seen-on-your-motorcycle/</link><description>A common piece of motorcycling wisdom is to "ride like everyone else is out to kill you". After two years of riding, here are some tricks I've learned to help motorists see me.</description><author>Dave Heinemann</author><pubDate>Mon, 26 Aug 2024 00:59:49 GMT</pubDate><guid isPermaLink="true">https://dheinemann.com/be-seen-on-your-motorcycle/</guid></item><item><title>Hetzner ARM Update Fix</title><link>https://geo.rocks/post/hetzner_arm_update/</link><description>&lt;p&gt;&lt;code&gt;Err:6 https://mirror.hetzner.com/ubuntu/packages jammy-updates/main arm64 Packages 404 Not Found [IP: 2a01:4ff:ff00::3:3 443]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;apt update&lt;/code&gt; &amp;amp; &lt;code&gt;apt upgrade&lt;/code&gt; not working anymore on a Hetzer ARM VM?&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s the solution.&lt;/p&gt;</description><author>Geography &amp;amp; Coding</author><pubDate>Sun, 25 Aug 2024 20:53:48 GMT</pubDate><guid isPermaLink="true">https://geo.rocks/post/hetzner_arm_update/</guid></item><item><title>Kipra Keyboard v2 Build Video</title><link>https://peterlyons.com/problog/2024/08/kipra-keyboard-v2-build-video/</link><description>&lt;p&gt;I made another version of the kipra "kinda pragmatic" split ergonomic keyboard. It's a minor incremental evolution of the first version. Key changes are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;thumb arc moved forward and outward quite a bit
&lt;ul&gt;
&lt;li&gt;the extreme placement of v1 turned out to be not as great ergonomically as I predicted&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;don't bother with hot swap sockets for the switches
&lt;ul&gt;
&lt;li&gt;I'm ride or die for the moment on Kailh Choc Ambient Nocturnal 20g silent linear switches&lt;/li&gt;
&lt;li&gt;I doubt there will be any switch available that I will prefer in the next few years&lt;/li&gt;
&lt;li&gt;direct soldering is more reliable and fewer components&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;simplify the perimeter geometry of the PCB
&lt;ul&gt;
&lt;li&gt;more pragmatic generally for fitting into cases, boxes, shelves&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;leave a blank area along the PCB perimiter for a bottom plate
&lt;ul&gt;
&lt;li&gt;also makes routing traces easier&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;reduced pinky stagger
&lt;ul&gt;
&lt;li&gt;standardized stagger unit of 1/4 of key size&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;no mounting holes
&lt;ul&gt;
&lt;li&gt;I ended up not needing them&lt;/li&gt;
&lt;li&gt;my new caseless approach is working great for low profile use&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="even-moar-pcbs-from-pcbway"&gt;Even MOAR PCBs from PCBWay&lt;/h2&gt;
&lt;p&gt;Once again the great &lt;a href="https://www.pcbway.com/"&gt;PCBWay&lt;/a&gt; was supportive enough to comp me these PCBs. I recommend them for your next ergogen keeb or any custom PCB project. I switched it up this time for the white color with black silkscreen and went on-theme for me travel case with matte black &amp;amp; white PLA filament as well as printing some tilted keycaps in matte black polytera PLA.&lt;/p&gt;
&lt;h2 id="full-build-video"&gt;Full Build Video&lt;/h2&gt;
&lt;p&gt;I filmed a full build of my 3rd assembled kipra v2. Mostly this will be for my own reference if I do another iteration, but it's there if anyone wants to watch.&lt;/p&gt;
&lt;div class="youtube-video-container"&gt;

&lt;/div&gt;
&lt;h2 id="key-point-solder-microcontroller-first-and-test-frequently"&gt;Key Point: Solder Microcontroller First and Test Frequently&lt;/h2&gt;
&lt;p&gt;The main idea I'd like to see adopted into the keeb zeitgeist is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Test the keyboard works frequently during the build&lt;/li&gt;
&lt;li&gt;As a corollary of that, &lt;strong&gt;the standard advice of doing diodes first is bad for beginners&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It's very easy to solder diodes, hotswap sockets, rotary encoders, interconnect jacks, etc and only as the last step of the build plug in the keyboard to find it does not type. It doesn't show up as a USB device to the OS.&lt;/p&gt;
&lt;p&gt;This is super disappointing and just no fun. I don't want folks to have to feel that feeling and then experience the chaotic troubleshooting it necessitates.&lt;/p&gt;
&lt;p&gt;But it's straightforward to avoid this by changing around the order of operations to focus on testability throughout the build.&lt;/p&gt;
&lt;h2 id="recommended-order-of-operations-for-building-a-keyboard"&gt;Recommended Order of Operations for Building a Keyboard&lt;/h2&gt;
&lt;p&gt;The build video above demonstrates this as well as showing several mid-build tests that failed and how I was able to focus my troubleshooting exclusively on the previous step and quickly correct problems.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;flash firmware on the MCUs first
&lt;ul&gt;
&lt;li&gt;confirm they work as keyboards&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder pin headers to MCU (left)
&lt;ul&gt;
&lt;li&gt;confirm again it still works as a keyboard&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder jumper pads on the PCB (left bottom side)
&lt;ul&gt;
&lt;li&gt;test with multimeter that jumpers are properly soldered&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder the MCU onto the PCB (left side, MCU on top side, solder on bottom side)
&lt;ul&gt;
&lt;li&gt;(optional) could test again here it still works as a keyboard&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder first diode closest in the matrix to the MCU (left bottom side)&lt;/li&gt;
&lt;li&gt;solder first switch (switch on left top, solder on bottom)
&lt;ul&gt;
&lt;li&gt;confirm it still works as a keyboard and that 1 switch types&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;now proceed with remaining diodes (left)
&lt;ul&gt;
&lt;li&gt;confirm each row with a multimeter and visually confirming all diodes are oriented correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder remaining switches (left)
&lt;ul&gt;
&lt;li&gt;confirm every key types correctly using a keystroke debugger like &lt;code&gt;xev&lt;/code&gt; or &lt;code&gt;Karabiner-Events-Viewer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder the TRRS (mount to left top, solder on left bottom)&lt;/li&gt;
&lt;li&gt;solder pin headers to MCU (right)
&lt;ul&gt;
&lt;li&gt;confirm again it still works as a keyboard&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder jumper pads on the PCB (right bottom side)
&lt;ul&gt;
&lt;li&gt;test with multimeter that jumpers are properly soldered&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder the MCU onto the PCB (right side, MCU on top side, solder on bottom side)
&lt;ul&gt;
&lt;li&gt;(optional) could test again here it still works as a keyboard&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder first diode closest in the matrix to the MCU (right bottom side)
&lt;ul&gt;
&lt;li&gt;careful that the diode orientation will be the opposite direction now&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder first switch (switch on right top, solder on bottom)
&lt;ul&gt;
&lt;li&gt;confirm it still works as a keyboard and that 1 switch types&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;now proceed with remaining diodes (right)
&lt;ul&gt;
&lt;li&gt;confirm each row with a multimeter and visually confirming all diodes are oriented correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder remaining switches (right)
&lt;ul&gt;
&lt;li&gt;confirm every key types correctly using a keystroke debugger like &lt;code&gt;xev&lt;/code&gt; or &lt;code&gt;Karabiner-Events-Viewer&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;solder the TRRS (mount to right top, solder on right bottom)&lt;/li&gt;
&lt;/ul&gt;</description><author>Pete's Points</author><pubDate>Sun, 25 Aug 2024 19:38:55 GMT</pubDate><guid isPermaLink="true">https://peterlyons.com/problog/2024/08/kipra-keyboard-v2-build-video/</guid></item><item><title>Data Marketplaces in a LLM World</title><link>https://magis.substack.com/p/data-marketplaces-in-a-llm-world</link><description>And Some Draft Ideas</description><author>Magis</author><pubDate>Sun, 25 Aug 2024 17:02:20 GMT</pubDate><guid isPermaLink="true">https://magis.substack.com/p/data-marketplaces-in-a-llm-world</guid></item><item><title>The Giant List of AI-Assisted Developer Tools Compared and Reviewed</title><link>https://zackproser.com/blog/ai-assisted-dev-tools-compared</link><description>A comprehensive comparison and review of AI-assisted developer tools, including code autocompletion, intelligent terminals/shells, and video editing tools.</description><author>Zachary Proser</author><pubDate>Sun, 25 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://zackproser.com/blog/ai-assisted-dev-tools-compared</guid></item><item><title>Discreet, Cheap, Film-Like EDC Street Photography Camera in 2024</title><link>https://thomashunter.name/posts/2024-08-25-discreet-cheap-street-photography-camera</link><author>Thomas Hunter II</author><pubDate>Sun, 25 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://thomashunter.name/posts/2024-08-25-discreet-cheap-street-photography-camera</guid></item><item><title>Tweaking desktop mode on the Steam Deck</title><link>https://gist.github.com/mhitza/ddfe3b52ce529039a89747afb8b11e44</link><description>The Steam Deck is a great piece of techonolgy, and very versatile thanks to the fact that its easy to switch to desktop mode. However the default configuration is rather limited, and there are a set of customizations I had to apply to make it fit my workflows.</description><author>personal code attic</author><pubDate>Sun, 25 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://gist.github.com/mhitza/ddfe3b52ce529039a89747afb8b11e44</guid></item><item><title>Evaluating your AI models in the wild</title><link>https://austinhenley.com/blog/aiinthewild.html</link><description>&lt;a href="https://austinhenley.com/blog/aiinthewild.html"&gt;https://austinhenley.com/blog/aiinthewild.html&lt;/a&gt;</description><author>Austin Z. Henley's Blog</author><pubDate>Sun, 25 Aug 2024 00:00:01 GMT</pubDate><guid isPermaLink="true">https://austinhenley.com/blog/aiinthewild.html</guid></item><item><title>Dev Logs Without the Noise</title><link>https://peterlyons.com/problog/2024/08/dev-logs-without-the-noise/</link><description>&lt;p&gt;For local development, json logs often have a bunch of fields you might want for production observability, but almost never care about for local development. The script below is a template you can use to filter out fields you know are noise while allowing all other fields to come through unchanged. It'll also pretty-print for easier reading. The basic approach is using &lt;code&gt;jq&lt;/code&gt; to delete noise fields with some looseness encoded to handle unexpected schema.&lt;/p&gt;
&lt;p&gt;The exact filters and selects will need to be tailored for your specific logs, but a project I recently worked on had things roughly as below that were usually noise I wanted to hide.&lt;/p&gt;
&lt;pre class="language-bash "&gt;&lt;code class="language-bash"&gt;# Delete the noisy fields we usually don&amp;#x27;t care about
# in dev and let jq pretty print and colorize
jq &amp;#x27;. |
    del(.request.ip?) |
    del(.duration?) |
    del(.time?) |
    del(.env?) |
    del(.user_id?) |
    del(.msg? | select(. == &amp;quot;request served&amp;quot;)) |
    del(.level? | select(. == &amp;quot;INFO&amp;quot;)) |
    if .msg? | contains(&amp;quot;[core]&amp;quot;) then empty else . end |
    del(.revision?)&amp;#x27;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use this, pipe your server's json log output to this script. You can throw &lt;code&gt;tee&lt;/code&gt; in the pipeline too if you want the full unfiltered log to disk, but the pretty version in your terminal.&lt;/p&gt;</description><author>Pete's Points</author><pubDate>Sat, 24 Aug 2024 23:12:16 GMT</pubDate><guid isPermaLink="true">https://peterlyons.com/problog/2024/08/dev-logs-without-the-noise/</guid></item><item><title>Write Yourself A Git</title><link>https://mfashby.net/posts/2024-08-24-wyag/</link><description>I've been working through 'Write youself a git'</description><author>Home on mfashby.net</author><pubDate>Sat, 24 Aug 2024 14:22:20 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2024-08-24-wyag/</guid></item><item><title>The Bookmarks Bin, August 2024</title><link>https://maximumeffort.substack.com/p/the-bookmarks-bin-august-2024</link><description>The second collection of interesting links I've come across</description><author>Maximum Effort, Minimum Reward</author><pubDate>Sat, 24 Aug 2024 03:39:42 GMT</pubDate><guid isPermaLink="true">https://maximumeffort.substack.com/p/the-bookmarks-bin-august-2024</guid></item><item><title>Clojuring the web application stack: Meditation One</title><link>https://www.evalapply.org/posts/clojure-web-app-from-scratch/index.html</link><description>In a land bereft of a canonical "killer app" web framework or two, one must think about the what, why, how, where of all the moving parts. Out here, one must become a student of web framework architecture in addition to web application architecture. For here, in Clojure-land, the two are one. ☯</description><author>Aditya Athalye's Eval/Apply Blog</author><pubDate>Sat, 24 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.evalapply.org/posts/clojure-web-app-from-scratch/index.html</guid></item><item><title>MicroGrad.jl: Part 5 MLP</title><link>https://liorsinai.github.io/machine-learning/2024/08/19/micrograd-5-mlp.html</link><description>A series on automatic differentiation in Julia. Part 5 shows how the MicroGrad.jl code can be used for a machine learning framework like Flux.jl. The working example is a multi-layer perceptron trained on the moons dataset.</description><author>Lior Sinai</author><pubDate>Sat, 24 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://liorsinai.github.io/machine-learning/2024/08/19/micrograd-5-mlp.html</guid></item><item><title>bang! bang! he murdered math! {the musical!}</title><link>https://taylor.town/bang-bang</link><description>Two shots from a revolving revolver will solv'er.</description><author>taylor.town</author><pubDate>Sat, 24 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/bang-bang</guid></item><item><title>Non-compete clauses won't go away unless we act</title><link>https://www.macchaffee.com/blog/2024/non-competes/</link><description>&lt;p&gt;If you're reading this blog, there's a high chance that you are one of the 1-in-5 workers who has a non-compete clause hiding in your employment contract. These are clauses that prevent workers from taking other jobs or starting other businesses that "compete" with the employer after leaving the job. I originally thought they were unique to highly-paid tech jobs, but actually &lt;a href="https://www.federalregister.gov/d/2024-09171/p-111"&gt;they are widespread&lt;/a&gt;, even among workers earning less than $40,000 per year.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://eig.org/state-noncompete-map/"&gt;After some states banned non-competes&lt;/a&gt;, the Federal Trade Commission (FTC) was able to quantify exactly how beneficial a ban on non-competes was in those states. The benefits are massive. Non-compete clauses suppress wages, reduce innovation, increase healthcare costs, and even discourage workers from reporting (and escaping from) illegal discrimination and abuse. They published &lt;a href="https://www.federalregister.gov/documents/2024/05/07/2024-09171/non-compete-clause-rule"&gt;570 pages of research&lt;/a&gt; on this topic, which is a good read, but you can read the &lt;a href="https://www.ftc.gov/system/files/ftc_gov/pdf/Non-Compete-Fact-Sheet.pdf"&gt;3-page factsheet&lt;/a&gt; to get the gist. All evidence points to non-competes being bad news.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Quick note: I am not a lawyer, this is not legal advice.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Thankfully, despite their widespread use and some state-wide bans, non-compete clauses are considered illegal under federal law since 2023. I say "considered", because their illegality rests on shaky ground right now. It rests on two pillars:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;In May 2023, the National Labor Relations Board (NLRB) issued a &lt;a href="https://www.nlrb.gov/news-outreach/news-story/nlrb-general-counsel-issues-memo-on-non-competes-violating-the-national"&gt;memo&lt;/a&gt; saying they consider non-competes to be a violation of the National Labor Relations Act (NLRA), since non-competes "interfere with, restrain, or coerce employees in the exercise of rights" such as the right to organize. This pillar is still intact.
&lt;ul&gt;
&lt;li&gt;They've also issued a &lt;a href="https://www.nlrb.gov/news-outreach/news-story/nlrb-general-counsel-issues-memo-with-guidance-to-regions-on-severance"&gt;memo&lt;/a&gt; saying they consider non-disparagement clauses illegal too.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;In April 2024, the FTC announced a &lt;a href="https://www.ftc.gov/news-events/news/press-releases/2024/04/ftc-announces-rule-banning-non-competes"&gt;a new rule&lt;/a&gt; which would have banned non-competes, based on those 570 pages of research I mentioned earlier. But before the rule took effect, &lt;a href="https://www.macchaffee.com/blog/2024/ftc_order.pdf"&gt;a district court blocked it&lt;/a&gt;, but the FTC can still address non-competes on a case-by-case basis. This pillar is crumbling, and faces an uncertain future in the Supreme Court.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Both of these pillars could be crushed to dust depending on who ends up controlling the executive branch of government.&lt;/p&gt;
&lt;h2 id="why-should-i-care"&gt;Why should I care?&lt;/h2&gt;
&lt;p&gt;While researching the effects of non-competes, the FTC received over &lt;a href="https://www.federalregister.gov/d/2024-09171/p-75"&gt;26,000 public comments&lt;/a&gt;, of which 96% were supportive of a ban. In these comments, I believe anyone can find a strong reason to care. (Content warning: abuse, sexual harassment)&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I am a journalist who has been forced to move across the country three times, and leave my field entirely for one year, in order to comply with stringent non-compete agreements.... In [one] situation, I was stuck working for abusive management who fostered a toxic and abusive workplace, and I had to work there for more than a year until I could find a job in another city entirely because they had threatened to sue me under the non-compete if I left and worked for another local station.... [E]ven if these clauses are unenforceable, as we've all heard before, who can afford the legal representation to go up against a corporation and their lawyers when the lawsuit threat comes? My life would have been very different if I weren't trapped by non-competes at points in my career.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I am being sued right now for going into business on my own in Boston, Massachusetts, by my former employer who says I signed a non-compete in 2003, 20 years ago.... I am fighting them in court. Hopefully I will prevail.... [The] corporation I worked for is a billion-dollar corporation. And they just keep trying scare tactics to make me back down. They went as far as trying to get a preliminary injunction ordered against me. And the judge refused but I still have to spend $1,000 an hour to defend myself.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;In October 2020, I started working as a bartender at a company called [REDACTED] for $10 an hour. On my first day, I unknowingly signed a 2-year non-compete, slipped between other paperwork while my boss rushed me, and downplayed its importance.... At [REDACTED], I was sexually harassed and emotionally abused. I needed money, so I searched for a new job while remaining at [REDACTED] for one year. I was eventually offered a bartending job at a family-owned bar with better wages, conditions, and opportunities. Upon resigning, I was threatened with a non-compete I didn't know existed. Still, I couldn't take it anymore, so believing it was an unenforceable scare tactic, I took the new job, thinking our legal system wouldn't allow a massive company with over 20 locations to sue a young entry-level worker with no degree. In December 2021, I was sued for $30,000 in “considerable and irreparable damages” for violating the non-compete...&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;I started my first job as a Nurse Practitioner in 2019. All positions I interviewed for required a non-compete.... In my case, I work for an employer that is hostile, discriminated against me during pregnancy and maternity leave and has raised his voice at me in meetings. He told me I was lucky to even have a job after becoming pregnant. I learned after starting at the practice that he has shown this pattern before with previous employees. I say this because all of these above-mentioned reasons are why I have the right to want to quit my job and move on. I desperately want to leave and start another job but I can't because of the non compete. I feel like a prisoner to my job. I feel depressed in my work conditions and I feel like I have no way out.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="what-can-i-do-about-it"&gt;What can I do about it?&lt;/h2&gt;
&lt;p&gt;Thankfully, even this crumbling legal basis is one that workers can capitalize on. You see, companies are generally risk-averse and they do not like the idea of potential &lt;a href="https://www.nlrb.gov/about-nlrb/what-we-do/investigate-charges"&gt;NLRB charges&lt;/a&gt; or potential lawsuits from the FTC. As the FTC &lt;a href="https://www.federalregister.gov/d/2024-09171/p-1456"&gt;found&lt;/a&gt;, there's no good evidence to suggest companies are materially harmed by the loss of non-competes, especially since they can protect their interests through NDAs and trade secret laws. So merely asking for the illegal clause to be removed from your (and your coworkers') contracts has a good chance of succeeding! All you have to do is ask.&lt;/p&gt;
&lt;p&gt;Here is a template email:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Subject: Remove illegal non-compete clause from employment contract&lt;/p&gt;
&lt;p&gt;Hello {name of HR representative},&lt;/p&gt;
&lt;p&gt;I noticed that my employment contract includes a non-compete clause which has been considered illegal by the National Labor Relations Board since 2023. The Federal Trade Commission also considers non-compete clauses to be illegal because they stifle innovation, exploit workers, hinder the economy, and even harm employers.&lt;/p&gt;
&lt;p&gt;https://www.nlrb.gov/news-outreach/news-story/nlrb-general-counsel-issues-memo-on-non-competes-violating-the-national&lt;/p&gt;
&lt;p&gt;https://www.ftc.gov/system/files/ftc_gov/pdf/Non-Compete-Fact-Sheet.pdf&lt;/p&gt;
&lt;p&gt;As a result, I'd like to formally request the removal of the non-compete clause from all standard employment contracts, including mine. This action will demonstrate that {company name} values innovation, fair competition, and the rights of its employees.&lt;/p&gt;
&lt;p&gt;Looking forward to your response,&lt;/p&gt;
&lt;p&gt;{your name}&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Tell your coworkers to send this email as well to support your case. Remember that the &lt;a href="https://www.nlrb.gov/about-nlrb/rights-we-protect/your-rights/interference-with-employee-rights"&gt;National Labor Relations Act&lt;/a&gt; protects your "right to join together to improve [your] working conditions, with or without a union".&lt;/p&gt;
&lt;h2 id="what-if-they-say-no"&gt;What if they say no?&lt;/h2&gt;
&lt;p&gt;I'd recommend reminding them of your options for reporting noncompliance with the law:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Labor rights violations can be &lt;a href="https://www.nlrb.gov/about-nlrb/what-we-do/investigate-charges"&gt;reported to the NLRB by filing a charge&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Violations of the FTC's rule against non-competes can be reported to &lt;a href="mailto:non-compete@ftc.gov"&gt;non-compete@ftc.gov&lt;/a&gt;. They can still choose to enforce the rule on a case-by-case basis.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It doesn't have to come to that though. You just need to inspire enough fear to convince a senior employee to look into it, and possibly spend the paltry (&lt;a href="https://www.federalregister.gov/d/2024-09171/p-2296"&gt;FTC-estimated&lt;/a&gt;) $670 of lawyer-time to change it.&lt;/p&gt;
&lt;p&gt;If they still say no, then this is a good history lesson that merely trying to convince powerful entities like companies to act contrary to their self-interest generally &lt;a href="https://www.radicalinprogress.org/kendi-2019-summary-part-4"&gt;does not work&lt;/a&gt;. Change is primarily accomplished through the wielding of power to enact policy changes. Another way to do that is to convince your elected representatives to ban non-competes, who are (in theory) self-interested in helping you. You can find contact information for your representatives &lt;a href="https://www.usa.gov/elected-officials/"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If you live in &lt;a href="https://eig.org/state-noncompete-map/"&gt;a state that already has strong bans&lt;/a&gt; against non-competes, then contact your senators to support House or Senate bills like the &lt;a href="https://www.congress.gov/bill/118th-congress/senate-bill/220"&gt;Workforce Mobility Act&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Otherwise, contact your state representatives to introduce a bill that bans non-competes in your state, or to support existing bills if you see one &lt;a href="https://eig.org/state-noncompete-map/"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The world has enough horribly exploitative things happening in it. Let's rid the world of this one.&lt;/p&gt;</description><author>Mac's Tech Blog</author><pubDate>Sat, 24 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.macchaffee.com/blog/2024/non-competes/</guid></item><item><title>Obsession</title><link>http://notes.eatonphil.com/2024-08-24-obsession.html</link><description>&lt;p&gt;In your professional and personal life, I don't believe there is a stronger motivation than having something in mind and the desire to do it. Yet the natural way to deal with a desire to do something is to justify why it's not possible.&lt;/p&gt;
&lt;p&gt;"I want to read more books but nobody reads books these days so how could I."&lt;/p&gt;
&lt;p&gt;"I want to write for a magazine but I have no experience writing professionally."&lt;/p&gt;
&lt;p&gt;"I want to build a company someday but how could someone of my background."&lt;/p&gt;
&lt;p&gt;Our official mentors, our managers, through a combination of well-intentioned defeatism and well-intentioned lack of accomplishment themselves, among other things, are often unable to process big goals or guide you toward them.&lt;/p&gt;
&lt;p&gt;I've been one of these managers myself. In fact I have, to my immense regret, tried too often to convince people to do what is practical rather than what they want to do. Or to do what I judged they were capable of doing rather than what they wanted to do.&lt;/p&gt;
&lt;p&gt;In the best cases, my listener had the self-confidence to ignore me. They did what they wanted to do anyway. In the worst case, again to my deep regret, I've been a well-intentioned part of derailing someone's career for years.&lt;/p&gt;
&lt;p&gt;So I don't want to convince anyone of anything anymore. If I start trying to convince someone by accident, I try to catch myself. I try to avoid sentences like "I think you should …". Instead "Here is something that's worked for me: …" or "Here is what I've heard works well for other people: …".&lt;/p&gt;
&lt;p&gt;Nobody wants to be convinced. But intelligent people will change their mind when exposed to new facts or different ideas. Being convinced is a battle of will. Changing one's mind is a purely personal decision.&lt;/p&gt;
&lt;p&gt;There are certainly people with discipline who can grind on things they hate doing and eventually become experts at it. But more often I see people grind on things they hate only to become depressed and give up.&lt;/p&gt;
&lt;p&gt;For most of us, our best hope is (healthy) obsession. And obsession, in the sense I'm talking about, does not come from something you are ambivalent about or hate. Obsession can only come when you're doing something you actually want to do.&lt;/p&gt;
&lt;p&gt;For big goals or big changes, you need regular commitment weekly, monthly, yearly. Over the course of years. And only obsession makes that work not actually feel like work. Obsession is the only thing that makes discipline not feel like discipline.&lt;/p&gt;
&lt;p&gt;That big goals take years to accomplish need not be scary. Obsession doesn't mean you can't pivot. There is quite a lot to gain by committing to something regularly over the course of years even if you decide to stop and commit from then on to something else. You will learn a good deal.&lt;/p&gt;
&lt;p&gt;And healthy obsession to me is more specifically measurable on the order of weeks, not hours or days. Healthy obsession means you're still building healthy personal and professional relationships. You're still taking care of yourself, emotionally and physically.&lt;/p&gt;
&lt;p&gt;I do not have high expectations for people in general. This seems healthy and reasonable. But as I meet more people and observe them over the years, I am only more convinced of the vast potential of individuals. Individuals are almost universally underestimated.&lt;/p&gt;
&lt;p&gt;I think you can do almost anything you want to do. If you commit to do doing it.&lt;/p&gt;
&lt;p&gt;I'll end this with a personal story.&lt;/p&gt;
&lt;p&gt;Until 11th grade, I hated school. I hated the rigidity. Being forced to be somewhere for hours and to follow so many rules. I skipped so many days of school I'm embarrassed by it. I'd never do homework at home. I never studied for tests. I got Bs and Cs in the second-tier classes. I was in the orchestra for 6 years and never practiced at home. I was not cool enough to be a "bad kid" but I did not understand the system and had no discipline whatsoever.&lt;/p&gt;
&lt;p&gt;I found out at the end of 10th grade that I could actually afford college if I got into a good enough school that paid full needs-based tuition. It sounded significantly better than the only other option that seemed obvious, joining the military as a recruit. I realized and decided that if I wanted to get into a good school I needed to not half-ass things.&lt;/p&gt;
&lt;p&gt;Somehow, I decided to only do things I could become obsessed with. And I decided to be obsessed in the way that I wanted, not to do what everyone else did (which I basically could not do since I had no discipline). If we covered a topic in class, I'd read news about it or watch movies about it. I'd get myself excited about the topic in every way I could.&lt;/p&gt;
&lt;p&gt;It basically worked out. I ended high school in the top 10% of the class (up from top 40% or something). I got into a good liberal arts college that paid the entirety of my tuition. But I remained a basically lazy and undisciplined person. I never stayed up late studying for a test. I dropped out after a year and a half for family reasons.&lt;/p&gt;
&lt;p&gt;But I've now spent the last 10 years in my spare time working on compiler projects, interpreter projects, parser projects, database projects, distributed systems projects. I've spent the last 6 years consistently publishing at least one blog post per month.&lt;/p&gt;
&lt;p&gt;I didn't want to work the way everyone else worked. I wanted to be obsessed about what I worked on.&lt;/p&gt;
&lt;p&gt;Obsession has made all of this into something I now barely register as doing. It's allowed me to continue adding activities like organizing book clubs and meetups to the list of things I'm up to. Up until basically this year I could have in good faith said I am a very lazy and undisciplined person. But obsession turned me into someone with discipline.&lt;/p&gt;
&lt;p&gt;Obsession became about more than just the tech. It meant trying to fully understand the product, the users, the market. It meant thinking more carefully about product documentation, user interfaces, company messaging. Obsession meant reflecting on how I treat my coworkers, and how my coworkers feel treated by others in general. Obsession meant wanting an equitable and encouraging work environment for everyone.&lt;/p&gt;
&lt;p&gt;And, as I said, it's about healthy obsession. I didn't really understand the "healthy" part until a few years ago. But I'm now convinced that the "healthy" part is as important as the "obsession" part. To go to the gym regularly. To play pickup volleyball. To cook excellent food. To read fiction and poetry and play music. To serve the community. To be friendly and encouraging to all people. To meet new people and build better genuine friendships.&lt;/p&gt;
&lt;p&gt;And in the context of work, "healthy obsession" means understanding you can't do everything, even while you care about everything. It means accepting that you make mistakes and that you do your best; that you try to do better and learn from mistakes the next time.&lt;/p&gt;
&lt;p&gt;It's got to be sustainable. And we can develop a healthy obsession while we have quite a bit of fun too. :)&lt;/p&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote an essay on my mistakes trying to convince people to do something, on doing what you want to do, and on obsession.&lt;br /&gt;&lt;br /&gt;Ended with a personal note on developing healthy discipline, and having fun. :)&lt;a href="https://t.co/4WWdtU6AhL"&gt;https://t.co/4WWdtU6AhL&lt;/a&gt; &lt;a href="https://t.co/lBw7zlqWeq"&gt;pic.twitter.com/lBw7zlqWeq&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1827373730781147241?ref_src=twsrc%5Etfw"&gt;August 24, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Sat, 24 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-08-24-obsession.html</guid></item><item><title>Book - Meltdown: Why Our Systems Fail and What We Can Do About It</title><link>https://mfashby.net/posts/2024-08-23-book-meltdown/</link><description>Short review of Meltdown: Why Our Systems Fail and What We Can Do About It</description><author>Home on mfashby.net</author><pubDate>Sat, 24 Aug 2024 00:04:46 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2024-08-23-book-meltdown/</guid></item><item><title>My New Public Service: PathfinderBeacon</title><link>https://heidenstedt.org/posts/2024/my-new-public-service-pathfinderbeacon/</link><description>&lt;p&gt;
      &lt;em&gt;Best viewed on the &lt;a href="https://heidenstedt.org/posts/2024/my-new-public-service-pathfinderbeacon/"&gt;original page&lt;/a&gt;, where extended functionality like the
    footnote helper is available.&lt;/em&gt;
    &lt;/p&gt;&lt;p&gt;In the midst of working on my project, &lt;a href="https://github.com/i5heu/ouroboros-db"&gt;ouroboros-db&lt;/a&gt;, I realized I needed a straightforward way to locate other nodes using the same RSA key. That&amp;rsquo;s when &lt;a href="https://github.com/i5heu/PathfinderBeacon"&gt;PathfinderBeacon&lt;/a&gt; came into being and that i release to the public with these words.&lt;/p&gt;
&lt;h2 id="what-is-pathfinderbeacon"&gt;&lt;a href="https://heidenstedt.org/posts/2024/my-new-public-service-pathfinderbeacon/#what-is-pathfinderbeacon"&gt;What is PathfinderBeacon?&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;&lt;a href="https://github.com/i5heu/PathfinderBeacon"&gt;PathfinderBeacon&lt;/a&gt; is a tool designed to make it easier to bootstrap your distributed systems using just a RSA key and one of the most robust dictionaries available: DNS. Since DNS inherently caches information at various levels, it allows PathfinderBeacon to deliver node addresses quickly and with minimal bandwidth usage. This means if multiple searches are conducted within the same network for the same room, there&amp;rsquo;s a high chance the requests won&amp;rsquo;t even leave your local network, thanks to the caching capabilities of your router&amp;rsquo;s local DNS.&lt;/p&gt;
&lt;p&gt;You can think of it like a DDNS (Dynamic Domain Name System) service, but you only need an RSA key to write to a subdomain. The subdomain is generated from the RSA key, and there is little surface for abuse since you can only publish highly sanitized TXT records.&lt;/p&gt;
&lt;h2 id="how-it-works"&gt;&lt;a href="https://heidenstedt.org/posts/2024/my-new-public-service-pathfinderbeacon/#how-it-works"&gt;How It Works&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Setting up is pretty simple. You either provide an RSA key or let PathfinderBeacon generate one for you. Once set, you can publish your room and node addresses, and anyone with the same RSA key can write to the same room, while anyone can read from it. This makes it easy to bootstrap your distributed systems without the need of running your own bootstrap service.&lt;/p&gt;
&lt;p&gt;Currently, there is an official Golang library for interacting with PathfinderBeacon, which you can find &lt;a href="https://github.com/i5heu/PathfinderBeacon-Client-Go"&gt;here&lt;/a&gt;. If you&amp;rsquo;re interested in developing libraries for other programming languages, feel free to reach out, and I&amp;rsquo;d be glad to assist and subsequently feature your work on the project repository.&lt;/p&gt;
&lt;h2 id="future-directions"&gt;&lt;a href="https://heidenstedt.org/posts/2024/my-new-public-service-pathfinderbeacon/#future-directions"&gt;Future Directions&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;For me this project is pretty much finished since it solves the underlying problem quite effectively, but i am fascinated by this problem and i would love to develop PathfinderBeacon further, maybe even in a distributed thingy without a central server, private rooms and much more is thinkable. You can checkout the &lt;a href="https://github.com/i5heu/PathfinderBeacon?tab=readme-ov-file#potential-future-features-and-ideas"&gt;README&lt;/a&gt; for more ideas.&lt;/p&gt;
&lt;p&gt;So, that’s PathfinderBeacon in a nutshell—a simple, secure way to manage node addresses within distributed systems. It&amp;rsquo;s a small project with big potential, and I&amp;rsquo;m excited to see how it can support and simplify the management of distributed networks.&lt;/p&gt;
&lt;p&gt;Thanks for reading, and I’m looking forward to constructive feedback or contributions!&lt;/p&gt;
&lt;p&gt;You can find PathfinderBeacon on &lt;a href="https://github.com/i5heu/PathfinderBeacon"&gt;GitHub&lt;/a&gt;&lt;/p&gt;</description><author>Mia Heidenstedt</author><pubDate>Fri, 23 Aug 2024 17:00:00 GMT</pubDate><guid isPermaLink="true">https://heidenstedt.org/posts/2024/my-new-public-service-pathfinderbeacon/</guid></item><item><title>home office with mobile data</title><link>https://evilcookie.de/home-office-with-mobile-data.html</link><description/><author>blog</author><pubDate>Fri, 23 Aug 2024 13:30:38 GMT</pubDate><guid isPermaLink="true">https://evilcookie.de/home-office-with-mobile-data.html</guid></item><item><title>How I Plan a Road Trip</title><link>https://dheinemann.com/how-i-plan-a-road-trip/</link><description>I take my motorcycle on a couple of road trips every year. Here's how I figure out where I'd like to go, and the sights and activities to do along the way.</description><author>Dave Heinemann</author><pubDate>Fri, 23 Aug 2024 11:09:14 GMT</pubDate><guid isPermaLink="true">https://dheinemann.com/how-i-plan-a-road-trip/</guid></item><item><title>ZLister Announcement</title><link>https://ztoz.blog/posts/zlister-announcement/</link><description>&lt;p&gt;ZLister, a to-do list management application, is now available. &lt;a href="https://zlister.ztoz.blog"&gt;ZLister&lt;/a&gt; allows you to create lists with entries and entries can be marked complete or incomplete (&amp;ldquo;to do&amp;rdquo;). ZLister&amp;rsquo;s user interface is designed for mobile devices, but may also be used on the desktop. ZLister does not store your data in the cloud and can run offline.&lt;!-- more --&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="ZLister Start Page" src="zlister-index-small.png" title="ZLister's start page showing lists" /&gt;&lt;/p&gt;
&lt;h2 id="motivation-and-goals"&gt;Motivation and Goals&lt;/h2&gt;
&lt;p&gt;For the last several years, I have been using Amazon&amp;rsquo;s Alexa app to store checklists of grocery items, books to read, and other miscellanea. However, as checklists are a side function within the app, I found the interface often clunky and the app required large amounts of resources to run for functionality I did not use. Furthermore, the app has recently added intrusive recommendations into the interface which emphasized its role as a channel for data exfiltration and ads.&lt;/p&gt;
&lt;p&gt;As a programmer, I decided to write my own application with the following goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Local-only. Beyond installing the application, there should not be any server or network calls.&lt;/li&gt;
&lt;li&gt;Low maintenance. The tech stack was chosen to work on a majority of devices and libraries and APIs that were unlikely to require any changes.&lt;/li&gt;
&lt;li&gt;Data freedom. Users can export and import data from files without any special intermediaries.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="features"&gt;Features&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Create named lists. Lists can contain entries and each entry has a short description.&lt;/li&gt;
&lt;li&gt;Entries can be toggled between incomplete and complete states.&lt;/li&gt;
&lt;li&gt;Lists and entries can be renamed, soft deleted, undeleted, and hard deleted.&lt;/li&gt;
&lt;li&gt;List data can be exported or imported.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Example List Page" src="zlister-shopping-small.png" title="Shopping list page showing items to pick up at store" /&gt;&lt;/p&gt;
&lt;h2 id="is-zlister-novel"&gt;Is ZLister novel?&lt;/h2&gt;
&lt;p&gt;No. To-do apps are often used as tutorial exercises and checklist functionality is widely available in many applications.&lt;/p&gt;
&lt;p&gt;Due to the commodification of this functionality, many implementations are distributed for free in return for data on user&amp;rsquo;s shopping habits and as a channel for advertisements. In contrast, ZLister was designed not to collect data and to have maintenance costs so low that monetization isn&amp;rsquo;t necessary.&lt;/p&gt;
&lt;h2 id="does-zlister-cost-anything-what-is-its-license"&gt;Does ZLister cost anything? What is its license?&lt;/h2&gt;
&lt;p&gt;There is no installation nor subscription costs for using ZLister. My costs are time and running a static site, which, barring unexpected exorbitant bandwidth costs, should be minimal.&lt;/p&gt;
&lt;p&gt;The source code is licensed under the GPLv3.&lt;/p&gt;
&lt;h2 id="what-is-the-tech-stack"&gt;What is the tech stack?&lt;/h2&gt;
&lt;p&gt;The app is written in &lt;a href="https://www.typescriptlang.org/"&gt;Typescript&lt;/a&gt; and uses HTML and standard Web APIs (no frameworks). There is one production dependency, a uuid library, which I might vendor as it is fairly small.&lt;/p&gt;
&lt;p&gt;For development, tests are in &lt;a href="https://jestjs.io/"&gt;Jest&lt;/a&gt;, browser tests use &lt;a href="https://playwright.dev/"&gt;Playwright&lt;/a&gt;, and &lt;a href="https://esbuild.github.io/"&gt;esbuild&lt;/a&gt; is the bundler/transpiler.&lt;/p&gt;
&lt;h2 id="what-browsers-are-supported-system-requirements"&gt;What browsers are supported? System requirements?&lt;/h2&gt;
&lt;p&gt;Theoretically, all browser should work. I primarily test against Firefox, although &lt;a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1907304"&gt;certain&lt;/a&gt; &lt;a href="https://bugzilla.mozilla.org/buglist.cgi?quicksearch=offline+cache"&gt;bugs&lt;/a&gt; make this difficult. I do not have an easy way to test Safari or iOS. The browser test suite runs against Firefox, Chrome, and Webkit, although certain tests are skipped against Webkit due to apparent test framework limitations.&lt;/p&gt;
&lt;p&gt;In my experience, offline mode has been the most error prone aspect.&lt;/p&gt;
&lt;p&gt;Disk storage requirements for the application are about 512 kb.&lt;/p&gt;</description><author>ℤ→ℤ</author><pubDate>Fri, 23 Aug 2024 05:50:39 GMT</pubDate><guid isPermaLink="true">https://ztoz.blog/posts/zlister-announcement/</guid></item><item><title>`*scratch*` v1.3 released</title><link>https://xenodium.com/scratch-v1-3-released</link><description>&lt;p&gt;It's been some time since the last release of &lt;code&gt;*scratch*&lt;/code&gt; for iOS. If you haven't heard about &lt;code&gt;*scratch*&lt;/code&gt;, it's a tiny app I built (part of &lt;a href="https://lmno.lol/alvaro/the-org-bundle"&gt;the org bundle&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;code&gt;*scratch*&lt;/code&gt; enables writing things on the go &lt;em&gt;as quickly as possible&lt;/em&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No need to create a new note.&lt;/li&gt;
&lt;li&gt;No need to bring keyboard up.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Just open and write&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Bonus: Basic Emacs org markup ;-)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;v1.3 (and yesterday's v1.2) are now available on the App Store. They are minor releases bringing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A monospaced font.&lt;/li&gt;
&lt;li&gt;A layout fix for &amp;quot;Settings &amp;gt; Display &amp;amp; Brightness &amp;gt; Display Zoom &amp;gt; Larger Text (often affecting iPhones with smaller screens).&lt;/li&gt;
&lt;li&gt;A menu fix.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/scratch-v1-3-released/scratch.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Are you a fan of this app? Want to &lt;a href="https://apps.apple.com/app/id1671420139?action=write-review"&gt;rate on the App Store&lt;/a&gt;? Please help me spread the word. Tell your friends.&lt;/p&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;center&gt;
  &lt;a href="https://apps.apple.com/app/id1671420139"&gt;
    &lt;img alt="download-on-app-store.png" src="https://xenodium.github.io/images/flat-habits-for-ios/download-on-app-store.png" width="180px" /&gt;
  &lt;/a&gt;
&lt;/center&gt;</description><author>xenodium.com @alvaro</author><pubDate>Fri, 23 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/scratch-v1-3-released</guid></item><item><title>Is it “/.well-known/”?</title><link>https://blog.ovalerio.net/archives/2830</link><description>Ironically, according to my experience, the .well-known directory doesn&amp;#8217;t do justice to its name. Even in use cases that would fit nicely in its original purpose.&amp;#160; But I&amp;#8217;m getting a bit ahead of myself. Let&amp;#8217;s first start with what it is, then move to discuss where it&amp;#8217;s used. But we&amp;#8217;ll do this rapidly, otherwise this [&amp;#8230;]</description><author>Gonçalo Valério</author><pubDate>Thu, 22 Aug 2024 19:37:21 GMT</pubDate><guid isPermaLink="true">https://blog.ovalerio.net/archives/2830</guid></item><item><title>Objective-C is just, like, a leaky abstraction over C</title><link>https://blog.metaobject.com/2024/08/objective-c-is-just-like-leaky.html</link><description>Recently I came across this 10 year old post by Robert Atkins: &lt;a href="https://frabjousdei.net/post/64096448121/objective-c-is-like-jimi-hendrix"&gt;Objective C is like Jimi Hendrix&lt;/a&gt;. It is about reconciling admiration for Objective-C by the "old-timers"  with the newcomers' somewhat less enthusiastic response.&lt;p&gt;

His very cogent insight was that Objective-C, like Jimi Hendrix, introduced new concepts that were somewhat revolutionary (""mind blowing") at the time, but are now taken for granted:

&lt;blockquote&gt;
So if you'e new to Objective-C and, as I am, struggling to come to terms with the fact that it's one great big leaky abstraction on top of C, put yourself in the shoes of an 80s C programmer and remember you get to use these neat "modern" features in a systems programming language.
&lt;/blockquote&gt;

As one of the very early, pre-NeXT, adopters of Objective-C, I have a slightly different take:
That implicit "despite" is actually very much a "because" for me.&lt;p&gt;

The modern features in Objective-C such as a dynamic messaging, a runtime with introspection and intercession etc. were not new at the time, and they were not really "mind-blowing".  LISP and Smalltalk had had them for a long time.  But so far having these features had required large, complex runtime environments that were very distant from the rest of the machine, and usually quite isolated from the rest of the machine.  Still are, to this day.  You don't program your computer with LISP or Smalltalk.  Your computer runs a separate LISP or Smalltalk computer that you can then program on its own terms and in its own world.  (And attempts to bring machines closer to these languages mostly did not succeed).&lt;p&gt;

Objective-C provided these features with the slimmest of a sliver of an extension to a portable PDP-11 macro assembler.&lt;p&gt;

Now &lt;em&gt;that&lt;/em&gt; was mind-blowing!&lt;p&gt;


And it goes beyond that:  there actually was, at some point, Objective-Assembler.  Objective-C was never intended to be "a language" like Java or Rust. The Objective part of Objective-C is a glue layer, an integration mechanism that can be added to any language.  There was Objective-FORTRAN, Objective-Pascal etc.  Helge Hess once put it almost perfectly:  Objective-C is COM with language support.  Or SOM. Or whatever interop mechanism they come up with again (Swift "library evolution", I am looking at you, can we have Objective-Swift?).&lt;p&gt;

So it wasn't just mind-blowing, it also was, is, and remains incredibly useful. &lt;p&gt;

So useful, in fact, that this slimmest sliver of an extension gradually replaced the portable PDP-11 macro assembler it was sitting on top of for most day-to-day use. So much so, that in the Apple developer ecosystem, the much larger C part started to be regarded as a completely separate language that most devs never dared touch.&lt;p&gt;

Of course the fact that this is doable shouldn't be surprising, after all the Objective part is modeled after Smalltalk, and Smalltalk is a complete programming language.  But Smalltalk requires a fairly large VM to run, typically coded in C or in Squeak's case, C plus BCPL-encoded-in-Smalltalk that gets automatically translated to C.  Objective-C and Objective-Assembler showed that you don't actually need all that, just a tiny messaging function on top of bare machine is not just sufficient, it's also faster, integrates better and is easier to implement.  Less is indeed more.&lt;p&gt;

All that doesn't mean that Objective-C isn't an ad-hoc car crash of two languages with overlapping functionality and syntax, and the safety of C's memory model and Smalltalk's type system.  That this disaster area works better in practice than most other languages tells you all you need to know about the state of PL design.&lt;p&gt;

A more principled, if irreverent, exploration of the same approach can be found in Ian Piumarta's &lt;a href="https://www.piumarta.com/papers/colas-whitepaper.pdf"&gt;COLA&lt;/a&gt;  (COmbined Lambda Architecture). He basically discovered the same principles, and came up with something that's even cooler, though at this point less practical. &lt;p&gt;

My favorite bit (it's a tight field) is how he manages to build a message-dispatcher that is itself invoked by message-send.  How does he resolve the infinite recursion inherent in the self-referential definition?  By pre-populating a single cache.&lt;p&gt;

Ian is also a really great speaker:&lt;p&gt;



Alas this video of his Stanford EE380 &lt;a href="https://web.stanford.edu/class/ee380/Abstracts/070214.html"&gt;talk&lt;/a&gt; is only 240p, so the text is illegible, but fortunately there are &lt;a href="http://piumarta.com/papers/EE380-2007-slides.pdf"&gt;slides&lt;/a&gt; that you can follow along.&lt;p&gt;

In Objective-S, the procedural/OO part is really based on just that sliver of an extension.  The C part is removed (yes, a real "Objective-C without the C"), replaced by a few type declarations for indicating primitive types, special stores for raw memory access when needed and a generalization of message-sending that subsumes calling C functions.&lt;p&gt;</description><author>metablog</author><pubDate>Thu, 22 Aug 2024 10:54:10 GMT</pubDate><guid isPermaLink="true">https://blog.metaobject.com/2024/08/objective-c-is-just-like-leaky.html</guid></item><item><title>2024-08-22-001</title><link>https://srijan.ch/notes/2024-08-22-001</link><description>I have been reading books mostly on Kindle for the last 10 years or so. Visited a nearby library today. I didn't realize I was missing the experience of browsing shelves, stumbling upon unexpected gems, getting lost in the recommendations section, and choosing something physical to checkout. Syndicated to: https://bsky.app/profile/did:plc:6koasqt256b6jwfrn74vwbg5/post/3l2br4drpjc2r</description><author>Srijan Choudhary, all posts</author><pubDate>Thu, 22 Aug 2024 06:20:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2024-08-22-001</guid></item><item><title>Note 148</title><link>https://qubyte.codes/notes/1724284527697</link><description>&lt;p&gt;HND ➡️ LHR&lt;/p&gt;</description><author>Qubyte Codes</author><pubDate>Thu, 22 Aug 2024 02:55:27 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1724284527697</guid></item><item><title>Idly musing about Manifest</title><link>https://paul.kinlan.me/idly-musing-about-manifest/</link><description>In this blog post, I share some findings from my exploration of HTTP Archive data.  I discovered that a significant number of websites using manifest.json files are using the default configuration generated by Create React App.  I plan to investigate this further and determine how prevalent default manifest files are across the web.</description><author>Modern Web Development with Chrome</author><pubDate>Wed, 21 Aug 2024 20:25:01 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/idly-musing-about-manifest/</guid></item><item><title>Anti-Pattern: Die perfekte Softwarearchitektur</title><link>https://backendhance.com/blog/2024/the-perfect-softwarearchitecture/</link><description>&lt;p&gt;Als junger Entwickler war ich naiv und ambitioniert. Ich suchte die perfekte Softwarearchitektur, einen Ansatz, den ich über alle Probleme stülpen könnte, unabhängig von der Branche, den Skalierungsanforderungen, den Sicherheitsmaßnahmen oder den Usern. Man müsste nur dieses eine Problem abstrakt lösen, dann könnte diese Architektur doch überall angewandt werden. Oder?&lt;/p&gt;
&lt;div class="alert d-flex alert-info"&gt;
&lt;i class="bx bx-info-circle lead me-3"&gt;&lt;/i&gt;
&lt;div&gt;
Dieser Artikel erschien erstmals im August 2024 als &lt;a href="https://www.golem.de/news/anti-pattern-die-perfekte-softwarearchitektur-2408-187665.html" rel="noopener noreferrer" target="_blank"&gt;Premiumartikel auf Golem.de&lt;i class="bx bx-link-external"&gt;&lt;/i&gt;&lt;/a&gt;
.
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;Dieser Artikel erklärt, warum die Suche nach der perfekten Softwarearchitektur eine Illusion ist. Aktuelle Trends und Moden diktieren oft universelle Lösungen, die jedoch die notwendige Flexibilität vermissen lassen, um im Markt zu bestehen. Das ist besonders relevant für technische Leiter und Entscheider im Mittelstand. Aber wie kann man eine Architektur gestalten, die flexibel genug ist, um sich an wechselnde Anforderungen anzupassen?&lt;/p&gt;</description><author>Backendhance</author><pubDate>Wed, 21 Aug 2024 17:14:55 GMT</pubDate><guid isPermaLink="true">https://backendhance.com/blog/2024/the-perfect-softwarearchitecture/</guid></item><item><title>Reactive Agents</title><link>https://paul.kinlan.me/projects/reactive-agents/</link><description>I'm exploring a new way to build reactive applications using an 'Agents' API.  Inspired by Preact Signals and my previous reactive-prompt project, this toolkit uses Chrome's prompt API.  Each Agent has a persona, task, and context, reacting to input changes.  You can chain Agents, passing data between them.  I've created different Agent types like a 'Human' Agent representing user input and a 'ToolCaller' that can execute JavaScript functions based on context.  This experiment explores data-flow-driven LLM applications, similar to Breadboard, and leverages Preact Signals for managing this flow.</description><author>Modern Web Development with Chrome</author><pubDate>Wed, 21 Aug 2024 16:42:00 GMT</pubDate><guid isPermaLink="true">https://paul.kinlan.me/projects/reactive-agents/</guid></item><item><title>Joining AngelList</title><link>https://faingezicht.com/articles/2024/08/21/angellist/</link><description>&lt;small&gt;&lt;em&gt;Este ensayo también está disponible &lt;a href="/articles/2024/08/21/angellist-es/"&gt;en español&lt;/a&gt;.&lt;/em&gt;&lt;/small&gt;

Finding a meaningful full-time job after founding a company isn't easy. &lt;a href="/articles/2024/05/20/reflections-on-first-startup/"&gt;After InScope&lt;/a&gt;, I wanted to work on something impactful, with a team that shared my interests and values, and which would help me grow as a leader. I took a short break to recharge batteries, then spent a few months consulting on engineering, data, and AI projects advising startups building across fintech, energy, and ecommerce. Eventually, I found[^1] the opportunity I was looking for at &lt;a href="https://www.angellist.com"&gt;AngelList&lt;/a&gt;.

As I started interviewing, AngelList seemed the perfect blend of my interest in startups, finance, and technology. Their position as an established player in the venture ecosystem didn't hurt either. AngelList started as a simple fundraising email list in 2010, but they've since reinvented themselves, and evolved into a full-fledged financial platform. I remember using their talent solution (now spun out as &lt;a href="https://wellfound.com/"&gt;Wellfound&lt;/a&gt;) when I was in college. Later, I also used their products as an angel investor, as an LP, and even considered running a &lt;a href="https://www.angellist.com/startups/ruv"&gt;roll up vehicle (RUV)&lt;/a&gt; as a founder. However, it wasn't until I heard Avlok, the CEO, &lt;a href="https://www.acquired.fm/episodes/angellist-ceo-avlok-kohli-on-the-transforming-the-company-and-venture-itself"&gt;on the Acquired podcast&lt;/a&gt; that I realized just how much AngelList had refocused and repositioned itself for growth. As I learned more about the internal narrative in my interviews, I was quickly hooked.

The company's short term goals align with some of &lt;a href="/articles/2024/08/20/third-wave-erp/"&gt;my own hypotheses&lt;/a&gt; of where the fintech space is headed. Among other projects, AngelList is doing its part in unbundling the stack by &lt;a href="https://web.archive.org/web/20240824021325/https://www.angellist.com/fund-erp"&gt;building a dedicated ERP for funds&lt;/a&gt;. The long term vision is even more exciting and ambitious.

Another reason I joined AngelList is the team. I’ve learned the hard way that who you work with is just as important as what you work on or how lucrative the market can be. The fact that a quarter of the employees were previous founders, the focus on accountability, and the lean resourcing, were all big draws. It's truly impressive that with just 35 engineers they've built a platform that manages $125B in assets and which is so central to how the ecosystem works. The team is talented, experienced, and every ex-employee and partner I spoke to gave me impeccable feedback. 

As for my specific role, I'll be leading a team focused on scaling infrastructure for funds, doubling down on my fintech experience. This might not sound like the most glamorous problem, but for those of us who care about enabling innovation, it's as critical as it gets. My team is building the data backbone that allows capital to flow efficiently through the venture ecosystem, making it easier for entrepreneurs to raise funds and for investors to manage their portfolios and partnerships. As I've written before, I tend to &lt;a href="/articles/2022/10/27/insurtech/"&gt;gravitate&lt;/a&gt; towards these kinds of second order effect problems, where the existence of a platform unlocks value for many other players. Having access to the trove of data behind all the internal quant research (not that different from my work on &lt;a href="https://medium.com/vouch-engineering/building-a-config-based-knowledge-graph-e073834f852a"&gt;the venture knowledge graph&lt;/a&gt; at Vouch) is a nice bonus.

My long-term goal remains the same: I will build my own company, and joining AngelList is a step in that direction. During the interviews, Xinran, the CTO, described the company as a halfway house for founders, emphasizing the hacker culture and the focus on shipping. While I had other offers on the table, some of which seemed like AI rocketships, I knew this was the right place for me. 

A week in, I’m just getting started, but I already have merged several PRs that are live in production. Next up, I'll be going deep on how the pieces come together to build the platform that will support the next generation of startups. If you’re also passionate about solving tough problems and excited to join a team shaping the future, &lt;a href="https://www.angellist.com/careers"&gt;we’re hiring&lt;/a&gt;.

&lt;small&gt;Thanks to Hannah Doherty and George Rider for their feedback on drafts of this post.&lt;/small&gt;

&lt;hr /&gt;

&lt;small&gt;
&lt;em&gt;Photos: Dyptich, Angel and Urban Palm by me. Previously posted on &lt;a href="/photos/2022/03/06/cdmx/"&gt;CDMX&lt;/a&gt;.
&lt;/em&gt;&lt;/small&gt;

[^1]:  Actually, *it* found me. Amazingly, I once again found the job through a cold email. This is the second time I am successfully recruited off of a Hacker News' *Who Wants To Be Hired* thread. The first time was when I joined Apple.</description><author>Avy Faingezicht</author><pubDate>Wed, 21 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://faingezicht.com/articles/2024/08/21/angellist/</guid></item><item><title>An engineers guide to the Solutions Architecture role</title><link>/solutions-architecture/</link><description>&lt;p&gt;For the past couple of years, I've worked as a solutions architect. Originally I stumbled into the role after being offered a contract, but found I'd been doing something akin to it for years. Coming from an engineering background (rather than business), I found I had to make lots of changes with how I approached work. This article is centred around those who are coming from an engineering background but have minimal exposure to business.&lt;/p&gt;
&lt;h2&gt;Defining the role&lt;/h2&gt;
&lt;p&gt;A solution architect can be difficult to define as they often have a broad remit. It can often include (ordered by technical involvement from low to high)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Enterprise Architect - working at an organization level to align IT strategy with business goals.&lt;/li&gt;
&lt;li&gt;Solutions Architect - Focuses on specific project designing end-to-end solutions based on business requirements and technical implementation.&lt;/li&gt;
&lt;li&gt;Technical Architect - Focuses on low level code based design decisions influencing the implementation heavily.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But, presuming we fit nicely in the second category, you will be responsible for:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Creating end-to-end designs for systems based on technical and business requirements.&lt;/li&gt;
&lt;li&gt;Innovating and introducing new technologies to the platform.&lt;/li&gt;
&lt;li&gt;Working with delivery teams to make sure they understand the designs created.&lt;/li&gt;
&lt;li&gt;Working with business users (and business analysts) to translate requirements and pain points into technical improvements.&lt;/li&gt;
&lt;li&gt;Creating PoCs for new systems.&lt;/li&gt;
&lt;li&gt;Selecting vendors to solve business problems.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This isn't an exhaustive list but gives you a flavour.&lt;/p&gt;
&lt;h2&gt;Understanding the business needs&lt;/h2&gt;
&lt;p&gt;This is the key role that transforms you from a developer into an architect. I'll admit, it's something I'm still working on. Unlike technical skills you can &amp;quot;train&amp;quot; by smashing leetcode, these softer skills cannot be approached in the same way. But there are some good patterns to follow:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Spend lots of time gathering requirements. I've always ended up making mistakes when I've rushed this part. This &amp;quot;discovery&amp;quot; should end up making up at least 60% of the overall project work - depending on the complexity. Principally you're trying to uncover the known unknowns, and the unknown unknowns. It's challenging to know when you're &amp;quot;done&amp;quot; here but a litmus test I do is  &amp;quot;could I explain how this project works down to the minute details to someone outside of this team?&amp;quot;. This stage involves carefully diagramming the current architecture. It also involves creating sequence, bpmn, or state machine diagrams (amongst others) that map out business processes, and other artifacts of the system. Use different diagramming systems based on the problem you're trying to solve. Consider, &amp;quot;what am I trying to communicate?&amp;quot;. Before moving onto the solution phase make sure to have all the business and technical requirements in one place. Then you will be able to validate all your possible solutions meet those requirements.&lt;/li&gt;
&lt;li&gt;Consider the solutions. Notice the plural, solutions. Although you may have a good idea of what the &amp;quot;best&amp;quot; idea is, it's vital to consider more than one solution. Why? Because you risk narrowing your focus and going for a solution that appeals to you rather than the business. Openly share the possible solutions with other architects, developers and engineering leaders. These ones will be able to share insights to improve your designs. They may also shed light on other systems that have an impact on your system - I find this especially true when being new to an organization.&lt;/li&gt;
&lt;li&gt;Handover - Once a solution has been selected, it's time to make sure it's documented so that the delivery teams can implement it. This phase involves things such as story mapping (what tickets do you need to build the thing), RFCs (for cross cutting technological decisions), ADRs (for project specific concerns), and workshops with developers. I've often found it useful to record a talk track which commentates the architecture diagrams and talks through how it all works.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Tools of the trade&lt;/h2&gt;
&lt;p&gt;In most organizations, the technology choices should be fairly well trodden - AWS, serverless, NoSQL databases, you get the idea. So what are the &amp;quot;tools&amp;quot; of an architect. Some have already been mentioned but here is a complete list of tools that I use regularly as part of my work.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Diagramming. There are a myriad of diagramming techniques. Try to learn as many as you can. If in doubt, ask others in your team. And when you feel confident enough, run a knowledge share session where you teach others about this technique. In general, for architectures use the C4 model. It's by far the most effective way of communicating technical designs. Ultimately, all diagramming is about effectively communicating to others about how a system or process works.&lt;/li&gt;
&lt;li&gt;Frameworks - Think TOGAF, AWS Well Architected and Zachman. These are frameworks that can be used to inform how you create system designs. In particular, the Well Architected framework provides a huge amount of value through its best practices. Make sure you can competently complete the well architected review and understand the terms within it.&lt;/li&gt;
&lt;li&gt;Documentation - In addition to diagrams, written documentation is a vital resource that you can provide to stakeholders and delivery teams. Becoming well adept at written communication is a great skill to have that will stand you in good stead. Documents you can produce (but not limited to), are RFCs, ADRs, Vendor comparison matrices and runbooks.&lt;/li&gt;
&lt;li&gt;Technical thinking - Coming from an engineering background you should have a good sense of if something you're designing will be secure, performant and cost effective (in that order). Make sure to validate these understandings against the real world by perhaps doing a proof of concept for your selected design. On this point, consider making yourself knowledgeable in things like the OWASP Top 10.&lt;/li&gt;
&lt;li&gt;Misc - I couldn't think of an umbrella term for this one. But, I've often found myself reaching for calculator.aws in order to provide a cost analysis/comparison for the systems I design. There are many other tools that can enrich the designs you do.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Measuring success&lt;/h2&gt;
&lt;p&gt;Engineers can easily measure success by looking at what is delivered. Solutions architects are an &amp;quot;enabler&amp;quot; - they don't &amp;quot;deliver&amp;quot; anything customer facing in the true sense of the word. But, as we have discussed, the success measure is that the project was discovered correctly (i.e., there were no further iterations required after new requirements came to light), good documentation was produced, and the designs were handed off smoothly to the team. Further, you can make sure that your systems were aligned with business goals and the costs matched what was expected. It's a challenge to be disconnected from delivery but still measure success - but it's still possible. And crucially this makes sure that you are delivering value.&lt;/p&gt;
&lt;h2&gt;Closing thoughts&lt;/h2&gt;
&lt;p&gt;I've enjoyed working as a solutions architect. Business was a weak area of mine and so it has been useful to understand and expand my knowledge. Further, the opportunity to teach and communicate (through diagrams, code and words) is what I love to do. I hope this small collection of thoughts helps you to become an architect!&lt;/p&gt;</description><author/><pubDate>Wed, 21 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">/solutions-architecture/</guid></item><item><title>Spectroscopy and Synthesis Quizzes</title><link>https://www.masterorganicchemistry.com/2024/08/20/spectroscopy-and-synthesis-quizzes/</link><description>Hey All! If the site has had a consistent weak spot over the past few years, it&amp;#8217;s been in the realms of spectroscopy and synthesis.</description><author>Master Organic Chemistry</author><pubDate>Tue, 20 Aug 2024 21:40:14 GMT</pubDate><guid isPermaLink="true">https://www.masterorganicchemistry.com/2024/08/20/spectroscopy-and-synthesis-quizzes/</guid></item><item><title>2024-08-20-001</title><link>https://srijan.ch/notes/2024-08-20-001</link><description>Webmention rocks tests Redoing these tests with indieConnector v2.1.0 Discovery Tests 1-22: PASS Discovery Test 23: FAIL Update Test 1: PASS Update Test 2: FAIL Delete Test 1: Not Tested Receiver Tests 1-2: PASS #IndieWeb #Webmention</description><author>Srijan Choudhary, all posts</author><pubDate>Tue, 20 Aug 2024 19:25:00 GMT</pubDate><guid isPermaLink="true">https://srijan.ch/notes/2024-08-20-001</guid></item><item><title>Installing data.table R  package on M1 Macs</title><link>https://www.ashish.zip/2024/08/installing-datatable-package-on-m1-macs.html</link><description>&lt;p&gt;&amp;nbsp;For data.table install, if you plan on using multiple threads, you have to enable it during installation/compilation. When loading the package, you will see a warning that says&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;data.table 1.x.x using 1 threads (see ?getDTthreads).&amp;nbsp;&lt;/p&gt;&lt;p&gt;********&lt;/p&gt;&lt;p&gt;This installation of data.table has not detected OpenMP support. It should still work but in single-threaded mode. This is a Mac.&lt;/p&gt;&lt;p&gt;********&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;Just go to this link:&lt;/p&gt;&lt;p&gt;https://github.com/Rdatatable/data.table/wiki/Installation&lt;/p&gt;&lt;p&gt;0. May need to install gfortran&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;brew install gfortran&lt;/span&gt;&lt;/p&gt;&lt;p&gt;and these lines are needed in ~/.R/makevars file:&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;LDFLAGS += -L/opt/homebrew/opt/libomp/lib -lomp&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;CPPFLAGS += -I/opt/homebrew/opt/libomp/include -Xclang -fopenmp&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: inherit;"&gt;1.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;curl -O https://mac.r-project.org/openmp/openmp-16.0.4-darwin20-Release.tar.gz&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;sudo tar fvx openmp-16.0.4-darwin20-Release.tar.gz&amp;nbsp;-C /&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: inherit;"&gt;This command will put these files in /usr/local/lib and /usr/local/include&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: inherit;"&gt;You can inspect the downloaded tar.gz file by uncompressing it and see how it is structured.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: inherit;"&gt;2.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;PKG_CPPFLAGS='-Xclang -fopenmp' PKG_LIBS=-lomp R CMD INSTALL /Users/ashish/downloads/data.table-1.15.4.tar.gz&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: inherit;"&gt;Just use the above command to install it with flags to detect the openMP install.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: inherit;"&gt;Next time you use the package, it should use multiple threads. Check this under R prompt after loading data.table package:&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;library(data.table)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;getDTthreads()&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;</description><author>Thoughts and ideas</author><pubDate>Tue, 20 Aug 2024 17:12:18 GMT</pubDate><guid isPermaLink="true">https://www.ashish.zip/2024/08/installing-datatable-package-on-m1-macs.html</guid></item><item><title>Project Hail Mary by Andy Weir</title><link>https://apurva-shukla.me/bookshelf/project-hail-mary/</link><description>⭐ ⭐ ⭐ ⭐ ⭐ Project Hail Mary by Andy Weir is an unputdownable read, chronicling an audacious mission to save the world. I can’t reveal too…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Tue, 20 Aug 2024 16:13:59 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/project-hail-mary/</guid></item><item><title>Ru by Kim Thúy</title><link>https://apurva-shukla.me/bookshelf/ru/</link><description>⭐ ⭐ ⭐ ⭐ Ru is a heart-wrenching tale of the aftermath of the Vietnam War, focusing on a family who were classified as bourgeois, but…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Tue, 20 Aug 2024 16:12:49 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/ru/</guid></item><item><title>Butter by Asako Yuzuki</title><link>https://apurva-shukla.me/bookshelf/butter/</link><description>⭐ ⭐ ⭐ I picked this book up because the cover looked so intriguing. Blood marks on a bright yellow page, with a black and white cow. Asako…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Tue, 20 Aug 2024 16:12:26 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/butter/</guid></item><item><title>Note 147</title><link>https://qubyte.codes/notes/1724158850059</link><description>&lt;p&gt;The super blue moon over Tokyo.&lt;/p&gt;

    &lt;img alt="The moon over Tokyo at night, as seen from the Sky Tree. It’s shortly after moonrise, so it’s bright orange in colour." src="https://qubyte.codes/images/1724158746722.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Tue, 20 Aug 2024 16:00:50 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1724158850059</guid></item><item><title>Maybe You Should Talk to Someone by Lori Gottlieb</title><link>https://apurva-shukla.me/bookshelf/maybe-you-should-talk-to-someone/</link><description>⭐ ⭐ ⭐ ⭐ ⭐ Maybe You Should Talk to Someone is one of those books that broadens your mind, because it revolves around understanding other…</description><author>Apurva Shukla's RSS Feed</author><pubDate>Tue, 20 Aug 2024 15:39:04 GMT</pubDate><guid isPermaLink="true">https://apurva-shukla.me/bookshelf/maybe-you-should-talk-to-someone/</guid></item><item><title>Update on Software Engineering Career Ladder</title><link>https://www.jamesshore.com/v2/blog/2024/update-on-software-engineering-career-ladder</link><description>&lt;p&gt;&lt;a href="https://www.jamesshore.com/v2/blog/2024/a-software-engineering-career-ladder"&gt;Back in April&lt;/a&gt;, I posted the new career ladder I was planning to introduce at OpenSesame, which I’ve joined as VP of Engineering. We rolled it out in July, so now’s a good time to share what we’ve learned so far.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.jamesshore.com/v2/blog/2024/career-ladder-skills.pdf"&gt;Here’s the latest version of the ladder.&lt;/a&gt;&lt;/strong&gt; (PDF)&lt;/p&gt;


&lt;h3&gt;Culture Changes&lt;/h3&gt;

&lt;p&gt;The purpose of the new career ladder is to help change the engineering culture at OpenSesame. The previous career ladder put a lot of emphasis on individual ownership and investigating new technologies. The new ladder focuses on teamwork, peer leadership, and maintainable code.&lt;/p&gt;

&lt;p&gt;So far, this seems to be working. My managers tell me that they’re seeing shifts in behavior, with people volunteering to lead meetings and take on work they didn’t before. We’ve also been able to use the new career ladder as a touchstone for people who are having performance problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict: So far, so good.&lt;/strong&gt;


&lt;h3&gt;Manager-Led Evaluation&lt;/h3&gt;

&lt;p&gt;The old career ladder was employee-led. Each employee was expected to take ownership of their progression by providing examples of their skills. Each example was entered into a spreadsheet and given a score of 1-5 by their manager (or rejected entirely). Once the employee had reached a certain score threshold, they were eligible for promotion.&lt;/p&gt;

&lt;p&gt;Employees felt that this approach was fair, as unbiased as these things can be, and gave clear direction about what they needed to accomplish. On the minus side, it required a lot of work, and some employees didn't put in the effort. It was inadvertently weighted toward “butt in seat” time and ability to self-promote.&lt;/p&gt;

&lt;p&gt;The new career ladder makes evaluations manager-led. Managers are expected to fill out a spreadsheet evaluating employees on a wide range of skills. Each skill is rated on the following scale:

&lt;ul&gt;
	&lt;li&gt;&lt;p&gt;&lt;em&gt;None:&lt;/em&gt; the employee doesn’t have the skill&lt;/p&gt;&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;em&gt;Learning:&lt;/em&gt; they’re still learning the skill&lt;/p&gt;&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;em&gt;Proficient:&lt;/em&gt; they can exercise the skill without help in some situations&lt;/p&gt;&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;em&gt;Fluent:&lt;/em&gt; the skill is second nature and they exercise it whenever it’s appropriate&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Promotion requires fluency in all the skills for a given title, including previous titles, although individual skills can be waived at managers’ discretion.&lt;/p&gt;

&lt;p&gt;This lighter-weight approach allows us to have a lot more skills, and we were hoping it would remove the bias the previous system had toward self-promotion and longevity. Our concern was that it would put a lot more burden on the individual managers to understand their employees and fill out their spreadsheets.&lt;/p&gt;

&lt;p&gt;As we’ve put it into practice, it’s definitely been a lot of work for managers to fill out the spreadsheets. For managers that know their employees well, the work’s been tolerable... mostly a matter of documenting what they already know. For managers that are new to their team, it’s been tough. They don’t have the intimate knowledge that filling out the spreadsheet requires, and it’s taking a lot of time to find out.&lt;/p&gt;

&lt;p&gt;You could argue that the system is working as intended, and that managers &lt;em&gt;should&lt;/em&gt; know their people well enough to fill out the spreadsheet. I tend to agree. It’s still a burden. The theory is that it’s only going to be difficult the first time, and then will get easier. We’ll see if that happens.&lt;/p&gt;

&lt;p&gt;The other open question is whether engineers feel this system is better. People had mixed feelings about the old system—they didn’t like the bias, but they did like how clear it was, and they thought it was fair if you were willing to put in the effort. I haven’t had a chance to go back and interview the engineers about what they think about the new system yet, but I’d like to do so.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Verdict: Jury’s still out on this one.&lt;/strong&gt;


&lt;h3&gt;Latest Update&lt;/h3&gt;

&lt;p&gt;In April, the ladder only covered up to Technical Lead. Now it also covers the advanced titles—Staff Engineer for the engineer track and three Engineering Manager titles for the management track. We still need to add Principal Engineer for the engineer track, and specialty skills, but this is enough for a foundation.&lt;/p&gt;

&lt;p&gt;The management track is the biggest addition, with 78 new skills. Just as the ladder sets new expectations of engineers, the management track sets new expectations for managers, with material about managing the system rather than just managing the work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.jamesshore.com/v2/blog/2024/career-ladder-skills.pdf"&gt;Read the full career ladder here.&lt;/a&gt;&lt;/strong&gt; (PDF)&lt;/p&gt;

&lt;p&gt;Here’s a summary of the titles and skills, with changes marked:&lt;/p&gt;

&lt;h4&gt;Associate Software Engineer&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Associate Software Engineers are just starting their software development careers. They're expected to understand the basics of software development, and be able to work in a professional setting, but they're mostly working under the guidance of more experienced engineers.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Baseline Expectations&lt;/strong&gt; &lt;em&gt;(was “Professionalism”)&lt;/em&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Spoken and written English&lt;/li&gt;
			&lt;li&gt;Work ethic&lt;/li&gt;
			&lt;li&gt;Intrinsic motivation&lt;/li&gt;
			&lt;li&gt;Remote attendance&lt;/li&gt;
			&lt;li&gt;In-person attendance&lt;/li&gt;
			&lt;li&gt;Active participation&lt;/li&gt;
			&lt;li&gt;&lt;strong&gt;Assume positive intent&lt;/strong&gt; &lt;em&gt;(new)&lt;/em&gt;&lt;/li&gt;
			&lt;li&gt;Respectful communication&lt;/li&gt;
			&lt;li&gt;Transparency&lt;/li&gt;
			&lt;li&gt;Team orientation&lt;/li&gt;
			&lt;li&gt;Follow the process&lt;/li&gt;
			&lt;li&gt;&lt;strong&gt;Persistence&lt;/strong&gt; &lt;em&gt;(was “Grit”)&lt;/em&gt;&lt;/li&gt;
			&lt;li&gt;Absorb feedback&lt;/li&gt;
			&lt;li&gt;Growth mindset&lt;/li&gt;
			&lt;li&gt;OpenSesame Qualified&lt;sup&gt;1&lt;/sup&gt;&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Classroom Engineering&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Object-oriented programming language&lt;/li&gt;
			&lt;li&gt;Pairing/teaming driver&lt;/li&gt;
			&lt;li&gt;Classroom-level debugging&lt;/li&gt;
			&lt;li&gt;Function and variable abstraction&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
&lt;/ul&gt;

&lt;p class="footnote"&gt;&lt;sup&gt;1&lt;/sup&gt;“OpenSesame Qualified” is our internal training program.&lt;/p&gt;


&lt;h4&gt;Software Engineer&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Software Engineers contribute to the work of their team without explicit guidance. They've begun to demonstrate peer leadership skills and are developing their abilities as generalizing specialists.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Communication&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Collective ownership&lt;/li&gt;
			&lt;li&gt;Defend a contrary stance&lt;/li&gt;
			&lt;li&gt;“Yes, and...”&lt;/li&gt;
			&lt;li&gt;Try it their way&lt;/li&gt;
			&lt;li&gt;Technical feedback&lt;/li&gt;
			&lt;li&gt;Active listening&lt;/li&gt;
			&lt;li&gt;As-built documentation&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Leadership&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Basic facilitation&lt;/li&gt;
			&lt;li&gt;Team steward&lt;/li&gt;
			&lt;li&gt;Valuable increment steward&lt;/li&gt;
			&lt;li&gt;Scut work&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Product&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Your team’s product&lt;/li&gt;
			&lt;li&gt;Your team’s customers and users&lt;/li&gt;
			&lt;li&gt;User story definition&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Implementation&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Your team’s programming language&lt;/li&gt;
			&lt;li&gt;Your team’s codebase&lt;/li&gt;
			&lt;li&gt;Basic test-driven development&lt;/li&gt;
			&lt;li&gt;Sociable unit tests&lt;/li&gt;
			&lt;li&gt;Narrow integration tests&lt;/li&gt;
			&lt;li&gt;End-to-end tests&lt;/li&gt;
			&lt;li&gt;Manual validation&lt;/li&gt;
			&lt;li&gt;Spike solutions&lt;/li&gt;
			&lt;li&gt;Basic SQL&lt;/li&gt;
			&lt;li&gt;Pairing/teaming navigator&lt;/li&gt;
			&lt;li&gt;Basic algorithms&lt;/li&gt;
			&lt;li&gt;Basic performance optimization&lt;/li&gt;
			&lt;li&gt;Debugging your team’s components&lt;/li&gt;
			&lt;li&gt;Simple dependency integration&lt;/li&gt;
			&lt;li&gt;Unhappy path thinking&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Design&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Decompose problem into tasks&lt;/li&gt;
			&lt;li&gt;Class abstraction&lt;/li&gt;
			&lt;li&gt;Mental model of your team’s codebase&lt;/li&gt;
			&lt;li&gt;Mental model of a complex dependency&lt;/li&gt;
			&lt;li&gt;Method and variable refactoring&lt;/li&gt;
			&lt;li&gt;Campsite rule&lt;/li&gt;
			&lt;li&gt;Fail fast&lt;/li&gt;
			&lt;li&gt;Paranoiac telemetry&lt;/li&gt;
			&lt;li&gt;Evaluate simple dependencies&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Operations&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Source control&lt;/li&gt;
			&lt;li&gt;Your team’s release process&lt;/li&gt;
			&lt;li&gt;On-call responsibility&lt;/li&gt;
			&lt;li&gt;On-call triaging&lt;/li&gt;
			&lt;li&gt;Issue investigation&lt;/li&gt;
			&lt;li&gt;Your team’s cloud infrastructure&lt;/li&gt;
			&lt;li&gt;Code vulnerability awareness&lt;/li&gt;
			&lt;li&gt;Cloud vulnerability awareness&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;Senior Software Engineer&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Despite the name, Senior Software Engineers are still fairly early in their careers. However, they have enough experience to take a strong peer leadership role in their teams. They've developed broader generalist skills and deeper specialist skills.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Communication&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Clear and concise speaking&lt;/li&gt;
			&lt;li&gt;Clear and concise writing&lt;/li&gt;
			&lt;li&gt;Technical diagramming&lt;/li&gt;
			&lt;li&gt;Explain mental model&lt;/li&gt;
			&lt;li&gt;Ensure everyone’s voice is heard&lt;/li&gt;
			&lt;li&gt;Coalition building&lt;/li&gt;
			&lt;li&gt;Interpersonal feedback&lt;/li&gt;
			&lt;li&gt;Runbook documentation&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Leadership&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Peer leadership&lt;/li&gt;
			&lt;li&gt;Comfort with ambiguity&lt;/li&gt;
			&lt;li&gt;Risk management&lt;/li&gt;
			&lt;li&gt;Intermediate facilitation&lt;/li&gt;
			&lt;li&gt;Mentoring and coaching&lt;/li&gt;
			&lt;li&gt;Critique the process&lt;/li&gt;
			&lt;li&gt;&lt;strong&gt;Build quality in&lt;/strong&gt; &lt;em&gt;(new)&lt;/em&gt;&lt;/li&gt;
			&lt;li&gt;Circles and soup&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Product&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Ownership&lt;/li&gt;
			&lt;li&gt;Vertical slices&lt;/li&gt;
			&lt;li&gt;Cost/value optimization&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Implementation&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;All of your team’s programming languages&lt;/li&gt;
			&lt;li&gt;All of your team’s codebases&lt;/li&gt;
			&lt;li&gt;Codebase specialty&lt;/li&gt;
			&lt;li&gt;Code performance optimization&lt;/li&gt;
			&lt;li&gt;Complex dependency integration&lt;/li&gt;
			&lt;li&gt;Retrofitting tests&lt;/li&gt;
			&lt;li&gt;Exploratory testing&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Design&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Codebase design&lt;/li&gt;
			&lt;li&gt;Simple design&lt;/li&gt;
			&lt;li&gt;Reflective design&lt;/li&gt;
			&lt;li&gt;Cross-class refactoring&lt;/li&gt;
			&lt;li&gt;Basic database design&lt;/li&gt;
			&lt;li&gt;Mental model of team dependencies&lt;/li&gt;
			&lt;li&gt;&lt;s&gt;Evaluate complex dependencies&lt;/s&gt; &lt;em&gt;(moved to Technical Lead)&lt;/em&gt;&lt;/li&gt;
			&lt;li&gt;Simplify and remove dependencies&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Operations&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Observability&lt;/li&gt;
			&lt;li&gt;Basic build automation&lt;/li&gt;
			&lt;li&gt;Basic deployment automation&lt;/li&gt;
			&lt;li&gt;Incident leader&lt;/li&gt;
			&lt;li&gt;Incident communicator&lt;/li&gt;
			&lt;li&gt;Incident fixer&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Senior SE Specialty&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Choose one of the specialty skill sets listed below.&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;Technical Lead&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Technical Leads are the backbone of a team. They combine deep expertise in several specialties with the ability to mentor and coach less experienced team members. They work closely with the team's other technical leads to advise engineering managers on the capabilities and needs of the team. However, this remains a coding-centric role, and the majority of their time is spent as a player-coach working alongside other team members.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Team Leadership&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Personal authority&lt;/li&gt;
			&lt;li&gt;Leaderful teams&lt;/li&gt;
			&lt;li&gt;Leadership specialty&lt;/li&gt;
			&lt;li&gt;Assess technical skills&lt;/li&gt;
			&lt;li&gt;Assess interpersonal skills&lt;/li&gt;
			&lt;li&gt;Assess product skills&lt;/li&gt;
			&lt;li&gt;Technical interview&lt;/li&gt;
			&lt;li&gt;Impediment removal&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Interpersonal Leadership&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Humility&lt;/li&gt;
			&lt;li&gt;Psychological safety&lt;/li&gt;
			&lt;li&gt;Calm the flames&lt;/li&gt;
			&lt;li&gt;Ignite the spark&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Product Leadership&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Options thinking&lt;/li&gt;
			&lt;li&gt;Status and forecasting&lt;/li&gt;
			&lt;li&gt;Progress and priorities&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Design Leadership&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;&lt;strong&gt;Pragmatic idealism&lt;/strong&gt; &lt;em&gt;(new)&lt;/em&gt;&lt;/li&gt;
			&lt;li&gt;Simple codebase architecture&lt;/li&gt;
			&lt;li&gt;&lt;s&gt;Reflective codebase architecture&lt;/s&gt; &lt;em&gt;(moved to Staff Engineer)&lt;/em&gt;&lt;/li&gt;
			&lt;li&gt;Risk-driven codebase architecture&lt;/li&gt;
			&lt;li&gt;Architectural refactoring&lt;/li&gt;
			&lt;li&gt;&lt;strong&gt;Evaluate complex dependencies&lt;/strong&gt; &lt;em&gt;(moved from Senior Software Engineer)&lt;/em&gt;&lt;/li&gt;
			&lt;li&gt;Published API design&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Technical Lead Specialties&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Choose three(?) additional specialty skill sets.&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the Technical Lead level, people can choose the Technical Track, which consists of the Staff Engineer and Principal Engineer titles, or the Management Track, which consists of the Manager and (eventually) Director titles.


&lt;h4&gt;Staff Engineer (New)&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Staff Engineers make a difference to the performance of Engineering as a whole. They spend time with each team in turn, working hands-on as player-coaches, and cross-pollinating information and ideas. They bring a breadth and depth of expertise that people are happy to learn from.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Departmental Leadership&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Cross-team personal authority&lt;/li&gt;
			&lt;li&gt;Management team personal authority&lt;/li&gt;
			&lt;li&gt;Cross-pollination&lt;/li&gt;
			&lt;li&gt;All teams’ products&lt;/li&gt;
			&lt;li&gt;All teams’ codebases&lt;/li&gt;
			&lt;li&gt;&lt;strong&gt;Reflective codebase architecture&lt;/strong&gt; &lt;em&gt;(moved from Senior Software Engineer)&lt;/em&gt;&lt;/li&gt;
			&lt;li&gt;Evaluate shared dependencies&lt;/li&gt;
			&lt;li&gt;Pedagogy&lt;/li&gt;
			&lt;li&gt;The heart of the matter&lt;/li&gt;
			&lt;li&gt;Identify systemic technical weaknesses&lt;/li&gt;
			&lt;li&gt;Identify systemic product weaknesses&lt;/li&gt;
			&lt;li&gt;Identify systemic process weaknesses&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;Principal Engineer (WIP)&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;This level hasn’t been defined yet.&lt;/em&gt;&lt;/p&gt;


&lt;h4&gt;Associate Manager of Engineering (New)&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Associate Engineering Managers work under the guidance of more senior managers to learn the craft of managing teams. In contrast to the Engineer Track, which focuses on peer leadership, managers focus on the team as a system, with an emphasis on people, process, and product.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manager Baseline&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Self-starter&lt;/li&gt;
			&lt;li&gt;Time management&lt;/li&gt;
			&lt;li&gt;Clear communication&lt;/li&gt;
			&lt;li&gt;Situational awareness&lt;/li&gt;
			&lt;li&gt;Responsiveness&lt;/li&gt;
			&lt;li&gt;Reliability&lt;/li&gt;
			&lt;li&gt;Organization&lt;/li&gt;
			&lt;li&gt;Detail orientation&lt;/li&gt;
			&lt;li&gt;Radical transparency&lt;/li&gt;
			&lt;li&gt;Disagree&lt;/li&gt;
			&lt;li&gt;Commit to vision&lt;/li&gt;
			&lt;li&gt;Commit to action&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic People Management&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Team rapport&lt;/li&gt;
			&lt;li&gt;Tailored approach&lt;/li&gt;
			&lt;li&gt;Performance evaluation&lt;/li&gt;
			&lt;li&gt;Performance development&lt;/li&gt;
			&lt;li&gt;Career development&lt;/li&gt;
			&lt;li&gt;Assess team dynamics&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Process Management&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Systems thinking&lt;/li&gt;
			&lt;li&gt;Understand the why&lt;/li&gt;
			&lt;li&gt;Delegation&lt;/li&gt;
			&lt;li&gt;Assess team process&lt;/li&gt;
			&lt;li&gt;Development resources&lt;/li&gt;
			&lt;li&gt;Management specialty&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Basic Product Management&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Engineering credibility&lt;/li&gt;
			&lt;li&gt;Go to gemba&lt;/li&gt;
			&lt;li&gt;Engineering partner&lt;/li&gt;
			&lt;li&gt;Assess team technical skills&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;Manager of Engineering (New)&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Engineering Managers are capable of managing teams without guidance. They're not about telling people what to do; instead, their job is to engineer a system that provides the team with the context, skills, resources, and peer leadership it needs to excel without management involvement.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced People Management&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Difficult conversations&lt;/li&gt;
			&lt;li&gt;Performance remediation&lt;/li&gt;
			&lt;li&gt;Improve team dynamics&lt;/li&gt;
			&lt;li&gt;Conflict resolution&lt;/li&gt;
			&lt;li&gt;Hiring manager&lt;/li&gt;
			&lt;li&gt;Diverse perspectives&lt;/li&gt;
			&lt;li&gt;Bench strength&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Process Management&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Manage the system&lt;/li&gt;
			&lt;li&gt;Improve team ownership&lt;/li&gt;
			&lt;li&gt;Improve team process&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Advanced Product Management&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Improve team technical skills&lt;/li&gt;
			&lt;li&gt;Chartering&lt;/li&gt;
			&lt;li&gt;Business context&lt;/li&gt;
			&lt;li&gt;Stakeholder context&lt;/li&gt;
			&lt;li&gt;Permeable shield&lt;/li&gt;
			&lt;li&gt;Stakeholder communication&lt;/li&gt;
			&lt;li&gt;Advanced forecasting&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Organizational Management&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Presentations&lt;/li&gt;
			&lt;li&gt;Spreadsheets&lt;/li&gt;
			&lt;li&gt;Administration&lt;/li&gt;
			&lt;li&gt;Procurement&lt;/li&gt;
			&lt;li&gt;Governance&lt;/li&gt;
			&lt;li&gt;Event planning&lt;/li&gt;
			&lt;li&gt;Team-level advisor&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
&lt;/ul&gt;


&lt;h4&gt;Senior Manager of Engineering (New)&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Senior Engineering Managers are leaders on the engineering management team. They look beyond their own team to how they can improve the performance of all engineering teams. They're mentors to other managers and advisors to senior management.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Management Leadership&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Management team leader&lt;/li&gt;
			&lt;li&gt;Cross-team initiatives&lt;/li&gt;
			&lt;li&gt;Cross-team pollination and growth&lt;/li&gt;
			&lt;li&gt;Cross-team process alignment&lt;/li&gt;
			&lt;li&gt;Identify systemic issues&lt;/li&gt;
			&lt;li&gt;Address systemic issues&lt;/li&gt;
			&lt;li&gt;Division-level advisor&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
	&lt;li&gt;&lt;p&gt;&lt;strong&gt;Process Design&lt;/strong&gt;&lt;/p&gt;
		&lt;ul&gt;
			&lt;li&gt;Process design mentoring&lt;/li&gt;
			&lt;li&gt;Change management&lt;/li&gt;
			&lt;li&gt;Principle: Rely on people&lt;/li&gt;
			&lt;li&gt;Principle: Deliver value&lt;/li&gt;
			&lt;li&gt;Principle: Eliminate waste&lt;/li&gt;
			&lt;li&gt;Principle: Seek technical excellence&lt;/li&gt;
			&lt;li&gt;Principle: Improve your process&lt;/li&gt;
			&lt;li&gt;Key idea: Build quality in&lt;/li&gt;
			&lt;li&gt;Key idea: Collective ownership&lt;/li&gt;
			&lt;li&gt;Key idea: Continuous improvement&lt;/li&gt;
			&lt;li&gt;Key idea: Embrace failure&lt;/li&gt;
			&lt;li&gt;Key idea: Face-to-face communication&lt;/li&gt;
			&lt;li&gt;Key idea: Feedback and iteration&lt;/li&gt;
			&lt;li&gt;Key idea: Fast feedback&lt;/li&gt;
			&lt;li&gt;Key idea: Last responsible moment&lt;/li&gt;
			&lt;li&gt;Key idea: Minimize work in progress&lt;/li&gt;
			&lt;li&gt;Key idea: Optimize for maintenance&lt;/li&gt;
			&lt;li&gt;Key idea: Self-organizing teams&lt;/li&gt;
			&lt;li&gt;Key idea: Simplicity&lt;/li&gt;
		&lt;/ul&gt;
	&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;Feedback&lt;/h3&gt;

&lt;p&gt;Please share your thoughts!&lt;/p&gt;

&lt;ul&gt;
	&lt;li&gt;&lt;a href="https://mastodon.online/@jamesshore/112991947950546149"&gt;Mastodon&lt;/a&gt;&lt;/li&gt;
	&lt;li&gt;&lt;a href="https://www.linkedin.com/posts/jdlshore_update-on-software-engineering-career-ladder-activity-7231483791293173760-iaAr"&gt;LinkedIn&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>James Shore</author><pubDate>Tue, 20 Aug 2024 05:12:01 GMT</pubDate><guid isPermaLink="true">https://www.jamesshore.com/v2/blog/2024/update-on-software-engineering-career-ladder</guid></item><item><title>McLuhan and Media Studies: An Underlooked Sub-discipline?</title><link>https://www.austinatchley.xyz/posts/2024/08/mcluhan-and-media-studies-an-underlooked-sub-discipline/</link><description>&lt;p&gt;I was first exposed to the ideas of the Canadian philosopher Marshall McLuhan in a talk by Chris Cox (CPO of Meta) at the Menlo Park campus. McLuhan&amp;rsquo;s work centers around media studies, which is a sub-discipline of philosophy that isn&amp;rsquo;t often taught in introductory or intermediate philosophy courses.&lt;/p&gt;
&lt;p&gt;After digging deeper into the work of McLuhan alone, I felt that it was strange I had never heard of some of these ideas given how predictive they proved to be. Perhaps the discipline is more valuable as an analytical tool rather than a predictive one, but it is certainly intriguing that people such as today&amp;rsquo;s Silicon Valley elite could use these ideas to build a predictive framework for steering the development of new technologies. Here&amp;rsquo;s a video I found recently that dives into a bit of McLuhan&amp;rsquo;s work:&lt;/p&gt;</description><author>Austin Atchley</author><pubDate>Tue, 20 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.austinatchley.xyz/posts/2024/08/mcluhan-and-media-studies-an-underlooked-sub-discipline/</guid></item><item><title>Unbundling and Third Wave ERPs</title><link>https://faingezicht.com/articles/2024/08/20/third-wave-erp/</link><description>&lt;small&gt;&lt;em&gt;Este ensayo también está disponible &lt;a href="/articles/2024/08/20/third-wave-erp-es/"&gt;en español&lt;/a&gt;.&lt;/em&gt;&lt;/small&gt;

In 2001, as the dot com boom popped, &lt;a href="https://www.computerworld.com/article/1337369/extended-erp-technology-reborn-in-b2b.html"&gt;InfoWorld wrote&lt;/a&gt; that the rise of B2B software was driving the rebirth of seemingly obsolete enterprise resource planning (ERP) technologies. In 2024, we are seeing another such ERP revival, but in this third wave no one is trying to build a monolithic one-size-fits-all tool anymore. The market is becoming increasingly fragmented, and we're about to see a thousand flowers bloom.

I spent &lt;a href="https://faingezicht.com/articles/2024/05/20/reflections-on-first-startup/"&gt;a good chunk of last year&lt;/a&gt; going deep into accounting software, and ERPs in particular, trying to figure out what made companies choose one tool over another, what users needed from their internal systems, and what bits forced them to upgrade. I quickly realized that "ERP" is a marketing term, and not a super meaningful one \- few companies have a real single operational source of truth, and most are in a constant synchronization battle across data sinks supported by a small army of engineers, consultants, and faulty pipelines. 

ERPs promise the integration of different business processes into a single centralized system. In theory teams across Finance, HR, Sales, etc. should all be able to operate out of the same integrated tool, ensuring consistency and reducing redundancy. In practice, teams feed data through SaaS solutions and an overworked accounting team reconciles what those tools produced to deliver as clear of a picture of the business as possible to the executive team, knowing full well that their reports are not accurate.

After talking to many finance professionals and operators in the fintech ecosystem, it became clear that the majority of modern companies don't need beasts like NetSuite or Workday, but they are also not served well by Quickbooks, Xero, nor other small players. They just don't have better options. All these tools are clunky, with 20-30 years of history and accumulated features, but finance and accounting teams are risk-averse and wary of trying unproven software that requires such deep connection to their business processes. New single-purpose systems are brought on piecemeal on top of the systems already in place, enhancing or automating processes as needed. This means that as companies scale, their centralized systems become ossified chimeras full of tacked-on API integrations and their path-dependent custom objects, causing both technical and organizational debt. Almost everyone I spoke with saw their ERP as a necessity, painful to maintain and operate, but also the only tool they trusted to get their work done. Mike Bianco puts it well &lt;a href="https://mikebian.co/vertical-saas-is-dead/"&gt;when he describes NetSuite&lt;/a&gt; as terrible software "that’s good enough for the people who need to use it every day." This dynamic is largely favorable to the incumbents.

The reason incumbent ERPs will lose relevance to entrants is not new AI-powered workflows or better and faster UX but the fact that they will be unbundled into niche versions of themselves, each with a small subset of the whole. The bajillion FP&amp;amp;A, billing, invoicing, and close management tools in the market show that there are feature-specific plays worth building. As software interfaces become more flexible, migrating processes out to new systems will become easier and easier. Beyond the startups cutting out horizontal slices, others are increasingly taking a vertical targeted approach, building end-to-end ERPs tailored to specific industries or business models. Companies like &lt;a href="https://www.helloturbine.com/"&gt;Turbine&lt;/a&gt;, for example, are creating comprehensive accounting and operational solutions for ecommerce brands, while &lt;a href="https://www.rillet.com/"&gt;Rillet&lt;/a&gt; is doing the same for SaaS businesses. Specialization allows startups to understand the pain points and requirements of their target market, enabling them to deliver better solutions. Each of those vertical solutions will be good enough in their own way.

As the concept of an ERP becomes more fluid, more and more functionalities are being extracted and offered as standalone products. Today, ERP software is at the center of business processes, but as the current players get chipped away into a &lt;a href="https://fintechfundamentals.substack.com/p/the-future-of-finance-tech"&gt;composable stack&lt;/a&gt; the landscape will shift and fragment significantly. Companies are already picking and choosing what they need, rather than being forced to adopt an entire suite of mediocre and irrelevant but integrated features. What makes an ERP an ERP today is its centrality, but in this new environment, the companies that succeed will be those that embrace the fragmentation.

&lt;hr /&gt;

&lt;small&gt;&lt;em&gt;Photo: Reforma, by me. Previously posted on &lt;a href="/photos/2022/03/06/cdmx/"&gt;CDMX&lt;/a&gt;.&lt;/em&gt;&lt;/small&gt;</description><author>Avy Faingezicht</author><pubDate>Tue, 20 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://faingezicht.com/articles/2024/08/20/third-wave-erp/</guid></item><item><title>Microsoft, XOAUTH2, and mbsync</title><link>https://blog.imraniqbal.org/microsoft-xoauth2-and-mbsync/</link><description>&lt;p&gt;I use neomutt (with mbsync) to access my email accounts.
This involves setting up an app password which retrieves messages via IMAP.
I have accounts across gmail, fastmail and microsoft email.&lt;/p&gt;
&lt;p&gt;All was well until June 19th.
Microsoft sent an email with the title "Action Needed – You may lose access to some of your third-party mail and calendar apps".
The TL;DR of this email is that MS will remove app passwords in September and I will need to switch to an oauth sign in flow to keep using neomutt, but if I fail to do that I can still use outlook applications 🙄.&lt;/p&gt;
&lt;p&gt;Getting the oauth flow was a bit of pain, so I'm documenting it here.&lt;/p&gt;
&lt;h3 id="step-1-get-mutt-oauth2-py"&gt;&lt;a class="anchor" href="https://blog.imraniqbal.org/microsoft-xoauth2-and-mbsync/#step-1-get-mutt-oauth2-py"&gt;
    #
&lt;/a&gt;
Step 1 Get mutt_oauth2.py&lt;/h3&gt;
&lt;p&gt;(neo)mutt comes with this file.
If your distribution does not include the file, you can grab it from the mutt git repository: &lt;a href="https://gitlab.com/muttmua/mutt/-/blob/master/contrib/mutt_oauth2.py" rel="external"&gt;https://gitlab.com/muttmua/mutt/-/blob/master/contrib/mutt_oauth2.py&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I also modified this file to use &lt;code&gt;pass&lt;/code&gt; instead of &lt;code&gt;gpg&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="step-2-create-an-oauth-application-in-azure"&gt;&lt;a class="anchor" href="https://blog.imraniqbal.org/microsoft-xoauth2-and-mbsync/#step-2-create-an-oauth-application-in-azure"&gt;
    #
&lt;/a&gt;
Step 2 Create an OAuth Application in Azure&lt;/h3&gt;
&lt;p&gt;Other blog posts on this subject (at least the ones that I could find with a search engine) recommended using Thunderbird's credentials.
The current credentials in source control is missing a &lt;code&gt;client_secret&lt;/code&gt; and the older credentials that are floating around on these posts do not work with my account type.
They with school/work accounts (anything belonging to an organization) but not individual accounts.&lt;/p&gt;
&lt;p&gt;During this process I changed the &lt;code&gt;redirect_uri&lt;/code&gt; in the mutt script to &lt;code&gt;http://localhost&lt;/code&gt; as that is what Thunderbird has.&lt;/p&gt;
&lt;p&gt;The easiest way forward was to create my own oauth application in azure and use its credentials.
Fair warning this involves giving microsoft your phone number, address, and credit card.&lt;/p&gt;
&lt;p&gt;AFAICT there is no cost associated with this: &lt;a href="https://learn.microsoft.com/en-us/answers/questions/1111512/application-registration-cost" rel="external"&gt;https://learn.microsoft.com/en-us/answers/questions/1111512/application-registration-cost&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I chose the "Pay as you go" on the azure sign up page.
In the search bar, look for "App registration" and create a new application.&lt;/p&gt;
&lt;p&gt;You will want to select the option that allows both organizational and personal accounts and because of the &lt;code&gt;redirect_uri&lt;/code&gt; stuff I did, I set the app type to web with &lt;code&gt;http://localhost&lt;/code&gt; as the redirect url.&lt;/p&gt;
&lt;p&gt;You'll get your &lt;code&gt;client_id&lt;/code&gt; as "Application (client) ID", "Certificates &amp;amp; secrets" will generate a &lt;code&gt;client_secret&lt;/code&gt;.
The next step will require both so make sure you note them down somewhere.&lt;/p&gt;
&lt;h3 id="step-3-run-mutt-oauth2-py"&gt;&lt;a class="anchor" href="https://blog.imraniqbal.org/microsoft-xoauth2-and-mbsync/#step-3-run-mutt-oauth2-py"&gt;
    #
&lt;/a&gt;
Step 3 Run mutt_oauth2.py&lt;/h3&gt;
&lt;p&gt;Run the script with &lt;code&gt;--authorize&lt;/code&gt;.
In my case, it's &lt;code&gt;./mutt_oauth2.py &amp;lt;pass-name&amp;gt; --authorize&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Follow the prompts, make sure you select &lt;code&gt;localhostauthcode&lt;/code&gt; when asked (this is where the localhost redirect comes into play).
It will print a url, visit in the browser and it will complete the token retrieval.&lt;/p&gt;
&lt;p&gt;The script store the &lt;code&gt;client_id&lt;/code&gt;, &lt;code&gt;client_secret&lt;/code&gt; and access + refresh tokens in pass.
&lt;code&gt;./mutt_oauth2.py &amp;lt;pass-name&amp;gt;&lt;/code&gt; will retrieve the access token, and also auto refresh if needed.&lt;/p&gt;
&lt;h3 id="step-4-update-mbsync"&gt;&lt;a class="anchor" href="https://blog.imraniqbal.org/microsoft-xoauth2-and-mbsync/#step-4-update-mbsync"&gt;
    #
&lt;/a&gt;
Step 4 Update mbsync&lt;/h3&gt;
&lt;p&gt;First mbsync needs to know to use XOAUTH2.
For arch install &lt;code&gt;cyrus-sasl-xoauth2-git&lt;/code&gt; from AUR.&lt;/p&gt;
&lt;p&gt;The important thing is the default value of &lt;code&gt;AuthMechs&lt;/code&gt; is &lt;code&gt;*&lt;/code&gt; which means mbsync will now attempt to use &lt;code&gt;XOAUTH2&lt;/code&gt; for every configured account.
For the microsoft accounts set &lt;code&gt;AuthMechs XOAUTH2&lt;/code&gt; and for all others set it as &lt;code&gt;AuthMechs PLAIN&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For the microsoft accounts change the &lt;code&gt;PassCmd&lt;/code&gt; to &lt;code&gt;/path/to/mutt_oauth2.py &amp;lt;pass-name&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At this point you're done and can continue using mbsync like you were before.
If you want to do this for gmail, the &lt;code&gt;AuthMechs&lt;/code&gt; value will be &lt;code&gt;OAUTHBEARER&lt;/code&gt;.
For the OAuth application setup refer to the 1,000,001 tutorials online.&lt;/p&gt;</description><author>Imran's Blog</author><pubDate>Tue, 20 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.imraniqbal.org/microsoft-xoauth2-and-mbsync/</guid></item><item><title>Vim Reference Guide book announcement</title><link>https://learnbyexample.github.io/vim-reference-guide-announcement/</link><description>&lt;p&gt;Hello!&lt;/p&gt;
&lt;p&gt;I am pleased to announce a new version of my &lt;strong&gt;Vim Reference Guide&lt;/strong&gt; ebook. This is intended as a concise learning resource for beginner to intermediate level Vim users. It has more in common with cheatsheets than a typical text book. Topics like Regular Expressions and Macros have more detailed explanations and examples due to their complexity. I hope this guide would make it much easier for you to discover Vim features and learning resources than my own blundering experience.&lt;/p&gt;
&lt;span id="continue-reading"&gt;&lt;/span&gt;&lt;br /&gt;
&lt;h2 id="release-offers"&gt;Release offers&lt;a class="zola-anchor" href="#release-offers"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To celebrate the new release, you can download PDF/EPUB versions of the ebook for FREE till 31-Aug-2024. You can still pay if you wish ;)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/vim_reference_guide"&gt;https://learnbyexample.gumroad.com/l/vim_reference_guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://leanpub.com/vim_reference_guide/c/new_vim_release"&gt;https://leanpub.com/vim_reference_guide/c/new_vim_release&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Two of my bundles are on sale as well:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/all-books/new_vim_release"&gt;All Books Bundle&lt;/a&gt; is $15 (normal price $32) — all my 13 programming ebooks&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learnbyexample.gumroad.com/l/linux-cli-text-processing/new_vim_release"&gt;Linux CLI Text Processing&lt;/a&gt; is 50% OFF — grep, sed, awk, perl and ruby one-liners, coreutils, cli computing&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="what-s-new"&gt;What's new?&lt;a class="zola-anchor" href="#what-s-new"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Updated ebook for &lt;strong&gt;Vim version 9.1&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Corrected typos&lt;/li&gt;
&lt;li&gt;Some of the examples, descriptions and external links were updated&lt;/li&gt;
&lt;li&gt;New cover image&lt;/li&gt;
&lt;/ul&gt;
&lt;br /&gt;
&lt;h2 id="videos"&gt;Videos&lt;a class="zola-anchor" href="#videos"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p align="center"&gt;&lt;/p&gt;
&lt;p&gt;Visit &lt;a href="https://www.youtube.com/playlist?list=PLTv2U3HnAL4NN2tK-59ZiNBm-o64-Yvos"&gt;this playlist&lt;/a&gt; for video demos on most of the topics from the ebook.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="testimonials"&gt;Testimonials&lt;a class="zola-anchor" href="#testimonials"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Got several suggestions and feedback when &lt;a href="https://news.ycombinator.com/item?id=30684232"&gt;my submission about this book&lt;/a&gt; reached the front page of Hacker News.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Great job on this! — rendall&lt;/p&gt;
&lt;p&gt;Hi, great work releasing this! Trying to explain vim concisely is always an interesting challenge and I had a great time reading your attempt in this book. I always find it really interesting on how people try to group certain vim functions in a way that makes sense to people that don't use vim. I think you cover that idea pretty well in your 'Vim philosophy and features' section whilst not making it overly abstract and keeping it relatable. — doix&lt;/p&gt;
&lt;p&gt;Neat stuff! One piece of feedback is that I would include &amp;quot;+p and &amp;quot;+yy in the copy and paste section. — mrpotato&lt;/p&gt;
&lt;p&gt;I learnt regular expression by reading your books, thank you for the great work. — LamJH&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;A comment from another &lt;a href="https://news.ycombinator.com/item?id=31931804"&gt;Hacker News thread&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I stumbled upon your vi post a few days ago, really like the style. Keep it up!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="vim-prank"&gt;Vim prank&lt;a class="zola-anchor" href="#vim-prank"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Did you know that Vim has an &lt;em&gt;easy&lt;/em&gt; mode? It can be rather hard to use for those already familiar with Vim modes. I wrote a &lt;a href="https://learnbyexample.github.io/mini/vim-prank/"&gt;blog post&lt;/a&gt; about this mode, which was interesting enough to reach the front page of Hacker News!&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="table-of-contents"&gt;Table of Contents&lt;a class="zola-anchor" href="#table-of-contents"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Preface&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Insert mode&lt;/li&gt;
&lt;li&gt;Normal mode&lt;/li&gt;
&lt;li&gt;Command-line mode&lt;/li&gt;
&lt;li&gt;Visual mode&lt;/li&gt;
&lt;li&gt;Regular Expressions&lt;/li&gt;
&lt;li&gt;Macro&lt;/li&gt;
&lt;li&gt;Customizing Vim&lt;/li&gt;
&lt;li&gt;CLI options&lt;/li&gt;
&lt;/ol&gt;
&lt;br /&gt;
&lt;h2 id="web-version"&gt;Web version&lt;a class="zola-anchor" href="#web-version"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You can also read the book online here: &lt;a href="https://learnbyexample.github.io/vim_reference/"&gt;https://learnbyexample.github.io/vim_reference/&lt;/a&gt;.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="github-repo"&gt;GitHub repo&lt;a class="zola-anchor" href="#github-repo"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Visit &lt;a href="https://github.com/learnbyexample/vim_reference"&gt;https://github.com/learnbyexample/vim_reference&lt;/a&gt; for markdown source and other details related to the book.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;img alt="info" src="/images/info.svg" /&gt; See also &lt;a href="https://learnbyexample.github.io/customizing-pandoc/"&gt;my blog post&lt;/a&gt; on how to customize &lt;code&gt;pandoc&lt;/code&gt; for generating beautiful PDF/EPUB versions from GitHub style markdown.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;br /&gt;
&lt;h2 id="newsletter"&gt;Newsletter&lt;a class="zola-anchor" href="#newsletter"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Subscribe to &lt;a href="https://learnbyexample.gumroad.com/l/learnbyexample-weekly"&gt;learnbyexample weekly&lt;/a&gt; — free newsletter covering programming resources, updates on what I am creating, tools, free ebooks and more, delivered every Friday.&lt;/p&gt;
&lt;br /&gt;
&lt;h2 id="feedback-and-errata"&gt;Feedback and Errata&lt;a class="zola-anchor" href="#feedback-and-errata"&gt;🔗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I would highly appreciate it if you'd &lt;strong&gt;let me know how you felt about this book&lt;/strong&gt;. It could be anything from a simple thank you, Gumroad rating, pointing out a typo, mistakes in code snippets, which aspects of the book worked for you (or didn't!) and so on. Reader feedback is essential and especially so for self-published authors.&lt;/p&gt;
&lt;p&gt;You can reach me via:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Issue Manager: &lt;a href="https://github.com/learnbyexample/vim_reference/issues"&gt;https://github.com/learnbyexample/vim_reference/issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;E-mail: &lt;code&gt;echo 'bGVhcm5ieWV4YW1wbGUubmV0QGdtYWlsLmNvbQo=' | base64 --decode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/learn_byexample"&gt;https://twitter.com/learn_byexample&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Happy learning :)&lt;/p&gt;</description><author>learnbyexample</author><pubDate>Tue, 20 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://learnbyexample.github.io/vim-reference-guide-announcement/</guid></item><item><title>What's the big deal about Deterministic Simulation Testing?</title><link>http://notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html</link><description>&lt;p&gt;Bugs in distributed systems are hard to find, largely because systems
interact in chaotic ways. And even once you've found a bug, it can be
anywhere from simple to impossible to reproduce it. It's about as far
away as you can get from the ideal test environment: property testing
a pure function.&lt;/p&gt;
&lt;p&gt;But what if we could write our code in a way that we can isolate the
chaotic aspects of our distributed system during &lt;i&gt;testing&lt;/i&gt;: run
multiple systems communicating with each other on a &lt;i&gt;single
thread&lt;/i&gt; and control all randomness in each system? And property
test this single-threaded version of the distributed system with
controlled randomness, all the while injecting faults (fancy term for
unhappy path behavior like errors and latency) we might see in the
real-world?&lt;/p&gt;
&lt;p&gt;Crazy as it sounds, people actually do this. It's called Deterministic
Simulation Testing (DST). And it's become more and more popular with
startups like FoundationDB, Antithesis, TigerBeetle, Polar Signals,
and WarpStream; as well as folks like Tyler Neely and Pekka Enberg,
talking about and making use of this technique.&lt;/p&gt;
&lt;p&gt;It has become so popular to talk about DST in my corner of the world
that I worry it risks coming off sounding too magical and maybe a
little hyped. It's worth getting a better understanding of both the
benefits and the limitations.&lt;/p&gt;
&lt;p&gt;Thank you to &lt;a href="https://www.linkedin.com/in/alexmillerdb/"&gt;Alex Miller&lt;/a&gt;
and &lt;a href="https://www.linkedin.com/in/will-wilson-330276112/"&gt;Will Wilson&lt;/a&gt;
for reviewing a version of this post.&lt;/p&gt;
&lt;h3 id="randomness-and-time"&gt;Randomness and time&lt;/h3&gt;&lt;p&gt;A big source of non-determinism in business logic is the use of random
numbers—in your code or your transitive dependencies or your language
runtime or your operating system.&lt;/p&gt;
&lt;p&gt;Crucially, DST does not imply you can't have randomness! DST merely
assumes that you have a global seed for all randomness in your program
and that the simulator controls the seed. The seed may change across
runs of the simulator.&lt;/p&gt;
&lt;p&gt;Once you observe a bad state as a result of running the simulation on
a random seed, you allow the user to enter the same seed again. This
allows the user to recreate the entire program run that led to that
observed bad state. Allows the user to debug the program trivially.&lt;/p&gt;
&lt;p&gt;Another big source of non-determinism is being dependent on time. As
with randomness, DST does not mean you can't depend on time. DST means
you must be able to control the clock during the simulation.&lt;/p&gt;
&lt;p&gt;To "control" randomness or time basically means you support dependency
injection, or the old-school alternative to dependency injection
called &lt;i&gt;passing the dependency as an explicit parameter&lt;/i&gt;. Rather
than referring to a global clock or a global seed, you need to be able
to receive a clock or a seed from someone.&lt;/p&gt;
&lt;p&gt;For example we might separate the operation of an application into the
language's &lt;code&gt;main()&lt;/code&gt; entrypoint and an actual application &lt;code&gt;start()&lt;/code&gt;
entrypoint.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# app.pseudocode&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# lots of business logic that might depend on time or do random things&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;clock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The application entrypoint is where we must be able to
swap out a real clock or real random seed for one controlled by our
simulator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# sim.pseudocode&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;app.pseudocode&amp;quot;&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;sim_clock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;make_sim_clock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;sim_seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DST_SEED&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_clock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sim_seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Bad execution at seed: &lt;/span&gt;&lt;span class="si"&gt;%s&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sim_seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let's look at another example.&lt;/p&gt;
&lt;h3 id="converting-an-existing-function"&gt;Converting an existing function&lt;/h3&gt;&lt;p&gt;Let's say that we had a helper method that kept calling a function
until it succeeded, with backoff.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# retry.pseudocode&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retry_backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There is a single source of nondeterminism here and it's where we
generate a seed. We could parameterize the seed, but since we want to
call &lt;code&gt;time.sleep()&lt;/code&gt; and since in DST we control the time, we can just
parameterize &lt;code&gt;time&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# retry.psuedocode&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Backoff&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retry_backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="n"&gt;this&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tries&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we can write a little simulator to test this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# sim.psuedocode&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;retry.pseudocode&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;sim_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;future&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;ms&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_time&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;failures&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;false&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;sim_time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;promise&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backoff&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retry_backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;sim_time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="n"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;promise&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;

    &lt;span class="n"&gt;assert_expect_failure_and_expected_time_elapse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_time&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;failures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Found logical error with seed: &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This demonstrates a few critical aspects of DST. First, the simulator
itself depends on randomness. But allows the user to provide a seed so
they can replay a simulation that discovers a bug. The controlled
randomness in the simulator is what lets us do property testing.&lt;/p&gt;
&lt;p&gt;Second, the simulation workload must be written by the user. Even when
you've got a platform like Antithesis that gives you an environment
for DST, it's up to you to exercise the application.&lt;/p&gt;
&lt;p&gt;Now let's get a little more complex.&lt;/p&gt;
&lt;h3 id="a-single-thread-and-asynchronous-io"&gt;A single thread and asynchronous IO&lt;/h3&gt;&lt;p&gt;The determinism of multiple threads can only be controlled at the
operating system or emulator or hypervisor layer. Realistically, that
would require third-party systems like Antithesis or
&lt;a href="https://github.com/facebookexperimental/hermit"&gt;Hermit&lt;/a&gt; (which, don't
get excited, is not actively developed and hasn't worked on any
interesting program of mine) or &lt;a href="https://rr-project.org/"&gt;rr&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;These systems transparently transform multi-threaded code into single
threaded code. But also note that Hermit and rr have only limited
ability to do fault injection which, in addition to deterministic
execution, is a goal of ours. And you can't run them on a mac. And
&lt;a href="https://github.com/rr-debugger/rr/issues/1373"&gt;can't&lt;/a&gt;
&lt;a href="https://github.com/facebookexperimental/hermit?tab=readme-ov-file#support"&gt;run&lt;/a&gt;
them on ARM.&lt;/p&gt;
&lt;p&gt;But we can, and would like, to write a simulator without writing a new
operating system or emulator or hypervisor, and without a third-party
system. So we must limit ourselves to writing code that can be
collapsed into a single thread. Significantly, since using blocking IO
would mean an entire class of concurrency bugs could not be discovered
while running the simulator in a single thread, we must limit
ourselves to asynchronous IO.&lt;/p&gt;
&lt;p&gt;Single threaded and asynchronous IO. These are already two big limitations.&lt;/p&gt;
&lt;p&gt;Some languages like Go are entirely built around transparent
multi-threading and blocking IO. Polar Signals
&lt;a href="https://www.polarsignals.com/blog/posts/2024/05/28/mostly-dst-in-go"&gt;solved&lt;/a&gt;
this for DST by compiling their application to WASM where it would run
on a single thread. But that wasn't enough. Even on a single thread,
the Go runtime intentionally schedules goroutines randomly. So Polar
Signals forked the Go runtime to control this randomness with an
environment variable. That's kind of crazy. Resonate took &lt;a href="https://github.com/resonatehq/resonate/blob/268c588e302f13187309e4b37636d19595d42fa1/internal/kernel/scheduler/coroutine.go"&gt;another
approach&lt;/a&gt;
that also looks cumbersome. I'm not going to attempt to describe
it. Go seems like a difficult choice of a language if you want to do
DST.&lt;/p&gt;
&lt;p&gt;Like Go, Rust has no builtin async IO. The most mature async IO
library is tokio. The tokio folks attempted to provide a
tokio-compatible &lt;a href="https://github.com/tokio-rs/simulator"&gt;simulator&lt;/a&gt;
implementation with all sources of nondeterminism removed. From what I
can tell, they did not at any point fully
&lt;a href="https://github.com/tokio-rs/tokio/issues/1845"&gt;succeed&lt;/a&gt;. That repo
has now been replaced with a "this is very experimental" tokio-rs
project called &lt;a href="https://github.com/tokio-rs/turmoil"&gt;turmoil&lt;/a&gt; that
provides deterministic execution plus network fault injection. (But
not disk fault injection. More on that later.) It isn't surprising
that it is difficult to provide deterministic execution for an IO
library that was not designed for it. tokio is a large project with
many transitive dependencies. They must all be combed for
non-determinism.&lt;/p&gt;
&lt;p&gt;On the other hand, Pekka has &lt;a href="https://github.com/penberg/hiisi/blob/main/hiisi-server/src/io/generic.rs"&gt;already
demonstrated&lt;/a&gt;
for us how we might build a simpler Rust async IO library that is
designed to be simulation tested. He modeled this on the TigerBeetle
design King and I
&lt;a href="https://tigerbeetle.com/blog/a-friendly-abstraction-over-iouring-and-kqueue"&gt;wrote&lt;/a&gt;
about two years ago.&lt;/p&gt;
&lt;p&gt;So let's sketch out a program that does buggy IO and let's look at how
we can apply DST to it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# readfile.pseudocode&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;into_buffer&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;read_buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n_read&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;into_buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_maybe_allocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;

    &lt;span class="n"&gt;into_buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_maybe_allocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In our simulator, we will provide a mocked out IO system and we will
randomly inject various errors while asserting pre- and
post-conditions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# sim.psuedocode&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;readfile.pseudocode&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DST_SEED&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DST_SEED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;sim_disk_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="n"&gt;MB&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;sim_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;eof&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;partial_read&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_in_range_inclusive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="n"&gt;memcpy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_disk_data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partial_read&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;partial_read&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_disk_data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EOF&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;partial_read&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;partial_read&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;sim_io&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;sim_fd&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;out_buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Vector&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;read_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;somefile&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out_buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;assert_bytes_equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;out_buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sim_disk_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Found logical error with seed: &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And with this simulator we would have eventually caught our partial
read bug! In our original program when we wrote:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;      &lt;span class="n"&gt;into_buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_maybe_allocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We should have written:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;      &lt;span class="n"&gt;into_buffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copy_maybe_allocate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_buffer&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;n_read&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Great! Let's get a little more complex.&lt;/p&gt;
&lt;h3 id="a-distributed-system"&gt;A distributed system&lt;/h3&gt;&lt;p&gt;I already mentioned in the beginning that the gist of deterministic
simulation testing a distributed system is that you get all of the
nodes in the system to run in the same process. This would be
basically impossible if you wanted to test a system that involved your
application plus Kafka plus Postgres plus Redis. But if your system is
a self-contained distributed system, such as one that embeds a Raft
library for high availability of your application, you can actually
run multiple nodes into the same process!&lt;/p&gt;
&lt;p&gt;For a system like this, our simulator might look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# sim.pseudocode&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;distsys-node.pseudocode&amp;quot;&lt;/span&gt;

&lt;span class="n"&gt;seed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DST_SEED&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DST_SEED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;rnd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="n"&gt;sim_fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# Inject random failure.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
         &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bad write'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# Inject random latency.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

      &lt;span class="n"&gt;n_written&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;assert_ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n_written&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# Inject random failure.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
         &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bad read'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# Inject random latency.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;sim_io&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;# Inject random failure.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bad open'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

      &lt;span class="c1"&gt;# Inject random latency.&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;sim_fd&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;all_ports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6001&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6002&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;nodes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;distsys&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;distsys&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;distsys&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sim_io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;all_ports&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_in_range_inclusive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;assert_valid_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Crash a process every so often&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.75&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;rnd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rand_in_range_inclusive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
      &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;catch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;Found logical error with seed: &lt;/span&gt;&lt;span class="si"&gt;%d&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;seed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I'm completely hand waving here to demonstrate the broader point and
not any specific testing strategy for a specific distributed
system. The important points are that these three nodes run in the
same process, on different ports.&lt;/p&gt;
&lt;p&gt;We control disk IO. We control network IO. We control how time
elapses. We run a deterministic simulated workload against the three
node system while injecting disk, network, and process faults.&lt;/p&gt;
&lt;p&gt;And we are constantly checking for an invalid state. When we get the
invalid state, we can be sure the user can easily recreate this
invalid state.&lt;/p&gt;
&lt;h3 id="other-sources-of-non-determinism"&gt;Other sources of non-determinism&lt;/h3&gt;&lt;p&gt;Within some error margin, most CPU instructions and most CPU behavior are
considered to be deterministic. There are, however, certain CPU
instructions that are &lt;a href="https://cs.stackexchange.com/questions/132842/under-which-conditions-a-given-program-is-deterministic-on-x86-64-machines/132856#132856"&gt;definitely
not&lt;/a&gt;. Unfortunately
that might
&lt;a href="https://github.com/facebookexperimental/hermit/issues/34"&gt;include&lt;/a&gt;
system calls. It might also
&lt;a href="https://stackoverflow.com/a/8171032"&gt;include&lt;/a&gt; malloc. There is very
little to trust.&lt;/p&gt;
&lt;p&gt;If we &lt;a href="https://antithesis.com/blog/deterministic_hypervisor/"&gt;ignore&lt;/a&gt;
Antithesis, people doing DST seem not to worry about these smaller
bits of nondeterminism. Yet it's generally agreed that DST is still
worthwhile anyway. The intuition here is that every bit of
non-determinism you can eliminate makes it that much easier to
reproduce bugs when you find them.&lt;/p&gt;
&lt;p&gt;Put another way: determinism, even among DST practitioners, remains a spectrum.&lt;/p&gt;
&lt;h3 id="considerations"&gt;Considerations&lt;/h3&gt;&lt;p&gt;As you may have noticed already from some of the pseudocode, DST is not a panacea.&lt;/p&gt;
&lt;h4 id="consideration-1:-edges"&gt;Consideration 1: Edges&lt;/h4&gt;&lt;p&gt;First, because you must swap out non-deterministic parts of your code,
you are not actually testing the entirety of your code. You are
certainly encouraged to keep the deterministic kernel large. But there
will always be the non-deterministic edges.&lt;/p&gt;
&lt;p&gt;Without a system like Antithesis which gives you an entire
deterministic machine, you can't test your whole program.&lt;/p&gt;
&lt;p&gt;But even with Antithesis you cannot test the &lt;i&gt;integration&lt;/i&gt; between your
system and external systems. You must mock out the external systems.&lt;/p&gt;
&lt;p&gt;It's also worth noting that there are many areas where you could
inject simulation. You could do it at a high-level RPC and storage
layer. This would be simpler and easier to understand. But then you'd
be omitting testing and error-handling of lower-level errors.&lt;/p&gt;
&lt;h4 id="consideration-2:-your-workload(s)"&gt;Consideration 2: Your workload(s)&lt;/h4&gt;&lt;p&gt;DST is dependent on your creativity and thoroughness of your workload
as much as any other type of test or benchmark.&lt;/p&gt;
&lt;p&gt;Just as you wouldn't depend on one single benchmark to qualify your
application, you may not want to depend on a single simulated
workload.&lt;/p&gt;
&lt;p&gt;Or as Will Wilson put it for me:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;The biggest challenge of DST in my experience is that tuning all the
random distributions, the parameters of your system, the workload,
the fault injection, etc. so that it produces interesting behavior
is very challenging and very labor intensive. As with fuzzing or
PBT, it's terrifyingly easy to build a DST system that appears to be
doing a ton of testing, but actually never explores very much of the
state space of your system. At FoundationDB, the vast majority of
the work we put into the simulator was an iterative process of
hunting for what wasn't being covered by our tests and then figuring
out how to make the tests better. This process often resembles
science more than it does engineering.&lt;/p&gt;
&lt;p&gt;Unfortunately, unlike with fuzzing, mere branch coverage in your
code is usually a pretty poor signal for the kinds of systems you
want to test with DST. At Antithesis we handle this with &lt;a href="https://antithesis.com/docs/best_practices/sometimes_assertions.html"&gt;Sometimes
assertions&lt;/a&gt;, at FDB we did something pretty similar, and I assume
TigerBeetle and others have their own version of this. But of course
the ultimate figure of merit is whether your DST system is finding
100% of your bugs. It's quite difficult to get to the point that it
does. The truly ambitious part of Antithesis isn't the hypervisor,
but the fact that we also aim to solve the much harder "is my DST
working?" problem with minimal human guidance or supervision.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 id="consideration-3:-your-knowledge-of-what-you-mocked"&gt;Consideration 3: Your knowledge of what you mocked&lt;/h4&gt;&lt;p&gt;When you mock out the behavior of disk or network IO, the benefits of
DST are tied to your understanding of the spectrum of behavior that
may happen in the real world.&lt;/p&gt;
&lt;p&gt;What are all possible error conditions? What are the extreme latency
bounds of the original method? What about corruption or misdirected
IO?&lt;/p&gt;
&lt;p&gt;The flipside here is that only in deterministic simulation testing can
you configure these crazy scenarios to happen at a &lt;i&gt;configurable
regularity&lt;/i&gt;. You can kick off a set of runs that have especially
high IO latency or especially high corrupt reads/writes. Joran and I
&lt;a href="https://tigerbeetle.com/blog/2023-07-11-we-put-a-distributed-database-in-the-browser"&gt;wrote&lt;/a&gt;
a year ago about how the TigerBeetle simulator does exactly this.&lt;/p&gt;
&lt;h4 id="consideration-4:-non-reproducible-seeds-as-code-changes"&gt;Consideration 4: Non-reproducible seeds as code changes&lt;/h4&gt;&lt;p&gt;Critically, the reproducibility of DST only helps so long as your &lt;i&gt;code
doesn't change&lt;/i&gt;. As soon as your code changes, the seed may no longer
even get you to the state where the bug was exhibited. So the
reproducibility of DST means more that it may help you convert the
seed simulation run into an integration test that describes the
precise scenario even as the code changes.&lt;/p&gt;
&lt;h4 id="consideration-5:-time-and-compute"&gt;Consideration 5: Time and compute&lt;/h4&gt;&lt;p&gt;Because of Consideration 4, you need to keep rerunning the simulator
not just to keep finding new seeds and new histories but because the
new seeds and new histories may change every time you make changes to
code.&lt;/p&gt;
&lt;h3 id="what-about-jepsen?"&gt;What about Jepsen?&lt;/h3&gt;&lt;p&gt;Jepsen does limited process and network fault injection while testing
for linearizability. It's a fantastic project.&lt;/p&gt;
&lt;p&gt;However, it represents only a subset of what is possible with
Deterministic Simulation Testing (if you actually put in the effort
described above to get there).&lt;/p&gt;
&lt;p&gt;But even more importantly, Jepsen has nothing to do with deterministic
execution. If Jepsen finds a bug and your system can't do
deterministic execution, you may or may not be able to reproduce that
Jepsen bug.&lt;/p&gt;
&lt;p&gt;Here's another Will Wilson
&lt;a href="https://antithesis.com/blog/is_something_bugging_you/"&gt;quote&lt;/a&gt; for you
on Jepsen and FoundationDB:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Anyway, we did [Deterministic Simulation Testing] for a while and
found all of the bugs in the database. I know, I know, that’s an
insane thing to say. It’s kind of true though. In the entire history
of the company, I think we only ever had one or two bugs reported by
a customer. Ever. Kyle Kingsbury aka “aphyr” didn’t even bother
testing it with Jepsen, because he didn’t think he’d find anything.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;&lt;p&gt;The degree to which you can place faith in DST alone, and not time
spent in production, has limits. However, it certainly does no harm to
employ DST. And, barring the considerations described above, will
likely make the kernel of your product significantly more
stable. Furthermore, everyone who uses DST knows about these
considerations. But I think it's worthwhile to list them out to help
folks who do not know DST to build an intuition for what it's
excellent at.&lt;/p&gt;
&lt;p&gt;Further reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4fFDFbi3toc"&gt;"Testing Distributed Systems w/ Deterministic Simulation" by Will Wilson&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.polarsignals.com/blog/posts/2024/05/28/mostly-dst-in-go"&gt;(Mostly) Deterministic Simulation Testing in Go&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/madsim-rs/madsim"&gt;Magical Deterministic Simulator for distributed systems in Rust&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;blockquote class="twitter-tweet"&gt;&lt;p dir="ltr" lang="en"&gt;I wrote a new post talking through the basics, considerations, and limitations of Deterministic Simulation Testing.&lt;a href="https://t.co/9Fp5ytL7Wz"&gt;https://t.co/9Fp5ytL7Wz&lt;/a&gt; &lt;a href="https://t.co/xRE6FOwc0P"&gt;pic.twitter.com/xRE6FOwc0P&lt;/a&gt;&lt;/p&gt;&amp;mdash; Phil Eaton (@eatonphil) &lt;a href="https://twitter.com/eatonphil/status/1825851204632445377?ref_src=twsrc%5Etfw"&gt;August 20, 2024&lt;/a&gt;&lt;/blockquote&gt; &lt;/p&gt;</description><author>Notes on software development</author><pubDate>Tue, 20 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://notes.eatonphil.com/2024-08-20-deterministic-simulation-testing.html</guid></item><item><title>Three simple answers to any executive's questions</title><link>https://swaits.com/the-three-answers-to-any-executive-question/</link><description>&lt;!-- ![me at my desk](swaits-at-desk.jpeg) --&gt;
&lt;p&gt;You're in a meeting with the SVP, the CEO, the President, head honcho, the big
fish. The conversation is fast-paced and moving between participants, when
suddenly she turns to you:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SWaits, are we green on our operational excellence goals?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Gulp!&lt;/p&gt;
&lt;h2 id="what-not-to-do"&gt;What NOT to do&lt;/h2&gt;
&lt;p&gt;I've seen this actually happen. An executive asks a question of someone and then
they proceed to share a word salad which never actually answers the question.
For example, answering the question I posed above:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;First of all, thanks for the question. It's a great question. Well, the goal
for ticket counts is going okay. We had some problems because of excuse one,
excuse two, and excuse three. But, we're working on it and going to make it
better. And then, I guess this other goal is going great. We really really had
a good time doing that one. Things worked out because we got lucky with our
estimates.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Blah! I'm so bored. This person is trying to sound impressive. They want you to
see how much they know and how smart they are.&lt;/p&gt;
&lt;p&gt;But, this is not clear or time efficient. It does absolutely nothing to help
your executive do her job. Furthermore, it never even answered her question.
After all, it was actually a &lt;em&gt;yes&lt;/em&gt; or &lt;em&gt;no&lt;/em&gt; question!&lt;/p&gt;
&lt;p&gt;When I see this happening in meetings, it feels like I'm watching a train-wreck
in slow motion. I think to myself, "PLEASE, FOR YOUR OWN SAKE, JUST ANSWER THE
QUESTION!"&lt;/p&gt;
&lt;p&gt;What's the right way to respond?&lt;/p&gt;
&lt;h2 id="use-the-executive-response-system"&gt;Use the Executive Response System&lt;/h2&gt;
&lt;p&gt;To make this easy to remember and simple to use, let's map it into a three-step
process...&lt;/p&gt;
&lt;h3 id="step-1-prepare-well-listen-carefully-and-pay-attention"&gt;Step 1. Prepare well, listen carefully, and pay attention&lt;/h3&gt;
&lt;p&gt;If you don't show up to the meeting fully prepared, you will fail. You should
anticipate the questions your executive will ask. Put yourself in her shoes
before the meeting. Brainstorm it with your team. Know your domain through and
through!&lt;/p&gt;
&lt;p&gt;And if you aren't engaged during the actual meeting, you won't be able to answer
effectively. Your executive is highly engaged. That's one of the many reasons
she has that job. Pay attention to every detail in the meeting!&lt;/p&gt;
&lt;p&gt;I know this might seem obvious, but it's better to say it than to assume.&lt;/p&gt;
&lt;h3 id="step-2-choose-one-of-three-responses"&gt;Step 2. Choose one of three responses&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;"Yes"&lt;/strong&gt;, &lt;strong&gt;"no"&lt;/strong&gt;, or &lt;strong&gt;"I'll find out"&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That's it! There are only three.&lt;/p&gt;
&lt;p&gt;Ok, I'm obviously over-simplifying things here. I want you to take this
directive in spirit. Your executive won't always ask you a simple yes or no
question. And you won't respond exactly with "yes" or "no". Think of it more of
the &lt;em&gt;affirmative&lt;/em&gt; or &lt;em&gt;negative&lt;/em&gt; equivalent.&lt;/p&gt;
&lt;p&gt;If it makes sense, use one of the three answers literally. If not, apply these
possible answers in a way that makes sense in the context of what you're being
asked.&lt;/p&gt;
&lt;p&gt;Bottom line, answer questions directly and concisely. Don't get too fancy with
narratives and explanations.&lt;/p&gt;
&lt;h3 id="step-3-provide-a-one-sentence-explanation-or-context"&gt;Step 3. Provide a one-sentence explanation or context&lt;/h3&gt;
&lt;p&gt;This one-sentence explanation is crucial. It provides just enough context
without overwhelming or derailing the conversation. For example:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Yes, we're green on our operational excellence goals. All key metrics are
trending positively, with a 15% improvement in efficiency over last quarter.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;No, we're not green on our operational excellence goals. We've hit unexpected
roadblocks in implementing the new ticketing system, causing delays.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;For "I'll find out" responses, always include a specific deadline If you don't
have the information at hand, it's perfectly acceptable to say so. But always
commit to a specific time when you'll provide the answer. For instance:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I'll find out and get back to you by end of day tomorrow. I need to confirm
the latest data with our analytics team.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="why-this-system-works"&gt;Why This System Works&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Clarity&lt;/strong&gt;: It provides a direct answer to the question asked. You were asked a
question. Answer it. No BS.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Efficiency&lt;/strong&gt;: It respects everyone's time by being concise. Executives are
busy people. Cut to the chase.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ownership&lt;/strong&gt;: It demonstrates responsibility when you don't have immediate
answers. Be accurate and forthright.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Confidence&lt;/strong&gt;: It shows you're prepared and in control of your domain. By
speaking as an executive expects, you demonstrate why you deserve your seat at
her table.&lt;/p&gt;
&lt;h3 id="implementing-the-executive-response-system"&gt;Implementing the Executive Response System&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Prepare&lt;/strong&gt;: Anticipate potential questions before meetings. Practice answering
"if I were the SVP" questions from your team.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implement&lt;/strong&gt;: Use the system in meetings. Don't stray. Control yourself.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Review&lt;/strong&gt;: Regularly assess your responses and refine your approach. After a
meeting, seek critical feedback from peers and managers on what you could've
done better.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Educate&lt;/strong&gt;: Share this system with your team to improve overall communication.
There's really nothing executive-specific here!&lt;/p&gt;
&lt;h3 id="but-what-if-she-asks-for-more"&gt;But, what if she asks for more?&lt;/h3&gt;
&lt;p&gt;Remember, this system is just the starting point. If the executive wants more
details, she'll ask. Be prepared to dive deeper if requested, but always
maintain the same level of clarity and directness.&lt;/p&gt;
&lt;p&gt;And there are going to be times this exact format simply won't work. But,
remember, answer the question and add a one sentence explanation. That's it.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Effective communication with executives doesn't have to be scary. By
implementing this simple system, you'll provide clear, actionable information
that enables better decision-making. You'll also position yourself as a reliable
and efficient team member. You'll earn trust from your executives!&lt;/p&gt;
&lt;p&gt;Next time you're in that high-stakes meeting and the big fish turns to you with
a question, take a deep breath. Remember: Yes, No, or I'll find out. Add a
concise explanation. You've got this!&lt;/p&gt;</description><author>swaits.com</author><pubDate>Tue, 20 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://swaits.com/the-three-answers-to-any-executive-question/</guid></item><item><title>Aa!! It's a zorting zombie!</title><link>https://datalars.com/2024/08/19/aa-its-a-zorting-zombie/</link><description>&lt;p&gt;I came across an interesting Bash issue today, as I was trying to restore a &lt;a href="https://en.wikipedia.org/wiki/Zstd"&gt;zstd&lt;/a&gt;-compressed &lt;a href="https://clonezilla.org/"&gt;CloneZilla&lt;/a&gt; Partclone image to a raw file in order to extract some data from it. For some reason, none of the solutions on the internet worked, and searching for the error message turned up no useful results. This was the command line I had constructed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ zstdcat nvme0n1p3.ntfs-ptcl-img.zst.* | sudo partclone.ntfs -C -r -W -s - -O image.img
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Notice the glob at the end of the only argument to &lt;code&gt;zstdcat&lt;/code&gt;. This only gave me:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Partclone v0.3.17 http://partclone.org
Starting to restore image (-) to device (image.img)
This is not partclone image.
Partclone fail, please check /var/log/partclone.log !
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;partclone&lt;/code&gt; kept saying &lt;code&gt;This is not partclone image&lt;/code&gt; no matter what I did. I did some sanity checking with the commands:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls nvme0n1p3.ntfs-ptcl-img.zst.*
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and also&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ ls nvme0n1p3.ntfs-ptcl-img.zst.a[a-x]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both returned the following list:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nvme0n1p3.ntfs-ptcl-img.zst.ab  nvme0n1p3.ntfs-ptcl-img.zst.ah  nvme0n1p3.ntfs-ptcl-img.zst.an  nvme0n1p3.ntfs-ptcl-img.zst.at
nvme0n1p3.ntfs-ptcl-img.zst.ac  nvme0n1p3.ntfs-ptcl-img.zst.ai  nvme0n1p3.ntfs-ptcl-img.zst.ao  nvme0n1p3.ntfs-ptcl-img.zst.au
nvme0n1p3.ntfs-ptcl-img.zst.ad  nvme0n1p3.ntfs-ptcl-img.zst.aj  nvme0n1p3.ntfs-ptcl-img.zst.ap  nvme0n1p3.ntfs-ptcl-img.zst.av
nvme0n1p3.ntfs-ptcl-img.zst.ae  nvme0n1p3.ntfs-ptcl-img.zst.ak  nvme0n1p3.ntfs-ptcl-img.zst.aq  nvme0n1p3.ntfs-ptcl-img.zst.aw
nvme0n1p3.ntfs-ptcl-img.zst.af  nvme0n1p3.ntfs-ptcl-img.zst.al  nvme0n1p3.ntfs-ptcl-img.zst.ar  nvme0n1p3.ntfs-ptcl-img.zst.ax
nvme0n1p3.ntfs-ptcl-img.zst.ag  nvme0n1p3.ntfs-ptcl-img.zst.am  nvme0n1p3.ntfs-ptcl-img.zst.as  nvme0n1p3.ntfs-ptcl-img.zst.aa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wait... &lt;em&gt;What?&lt;/em&gt; Notice how &lt;code&gt;ab&lt;/code&gt; is the first and &lt;code&gt;aa&lt;/code&gt; is the &lt;em&gt;last&lt;/em&gt; substitution. Obviously, &lt;code&gt;aa&lt;/code&gt; needs to be the first substitution! They need to be &lt;code&gt;zstdcat&lt;/code&gt;ed &lt;em&gt;in order&lt;/em&gt; for &lt;code&gt;partclone&lt;/code&gt; to recognize the file, as I assume there's a file signature/magic bytes at the start of the raw file contained within the archives.&lt;/p&gt;
&lt;p&gt;My question was, why is my alphabetization seemingly broken? According to a web search, Bash globs will always return an ordered/alphabetized list. I was able to start the restore process by manually entering the list of files in order instead of globbing, but curiosity got the better of me and I had an inkling this was related to my system locale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;SPOILERS&lt;/strong&gt;: It was.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ locale
LANG=en_US.UTF-8
LANGUAGE=
LC_CTYPE=&amp;quot;en_US.UTF-8&amp;quot;
LC_NUMERIC=nb_NO.UTF-8
LC_TIME=en_GB.UTF-8
LC_COLLATE=nb_NO.UTF-8
LC_MONETARY=nb_NO.UTF-8
LC_MESSAGES=&amp;quot;en_US.UTF-8&amp;quot;
LC_PAPER=nb_NO.UTF-8
LC_NAME=nb_NO.UTF-8
LC_ADDRESS=nb_NO.UTF-8
LC_TELEPHONE=nb_NO.UTF-8
LC_MEASUREMENT=nb_NO.UTF-8
LC_IDENTIFICATION=nb_NO.UTF-8
LC_ALL=
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Observe how &lt;code&gt;LC_COLLATE=nb_NO.UTF-8&lt;/code&gt;. I have my system language set to English, but most other locale settings set to Norwegian. In Norwegian, &lt;code&gt;Aa&lt;/code&gt;/&lt;code&gt;aa&lt;/code&gt; is a common substitution for &lt;code&gt;Å&lt;/code&gt;/&lt;code&gt;å&lt;/code&gt;, the last character of the Norwegian alphabet. The sorting algorithm, in its infinite wisdom, seems to have decided that a file extension of &lt;code&gt;*.aa&lt;/code&gt;should be sorted at the very end because of this, which breaks the argument list.&lt;/p&gt;
&lt;p&gt;To fix this, I set &lt;code&gt;LC_COLLATE&lt;/code&gt; to &lt;code&gt;C&lt;/code&gt; by issuing:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ sudo localectl set-locale LC_COLLATE=C
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This worked for about three seconds before KDE decided that my opinion is wrong, and promptly overwrote it, resurrecting &lt;code&gt;nb_NO.UTF-8&lt;/code&gt; like a digital zombie.&lt;/p&gt;
&lt;p&gt;In KDE's System Settings &amp;gt; Region &amp;amp; Language section, there are a few locale related settings, but nothing about sorting. Presumably, it uses one of the other fields to &lt;em&gt;assume&lt;/em&gt; the value of &lt;code&gt;LC_COLLATE&lt;/code&gt;, and you know what they say about assuming.&lt;/p&gt;
&lt;p&gt;To &lt;em&gt;actually&lt;/em&gt; fix it, I added &lt;code&gt;export LC_COLLATE=&amp;quot;C&amp;quot;&lt;/code&gt; to my &lt;code&gt;~/.bashrc&lt;/code&gt;, which seems to work, and persists between terminal sessions.&lt;/p&gt;</description><author>datalars</author><pubDate>Mon, 19 Aug 2024 22:47:48 GMT</pubDate><guid isPermaLink="true">https://datalars.com/2024/08/19/aa-its-a-zorting-zombie/</guid></item><item><title>CSV Viewer with charts</title><link>https://newbeelearn.com/tools/csvonline/</link><description>CSV Viewer with charts, supports viewing CSV, sorting, searching, filtering of data, plotting charts, setting title and saving charts as images.</description><author>Welcome to my weblog on Newbeelearn</author><pubDate>Mon, 19 Aug 2024 21:31:23 GMT</pubDate><guid isPermaLink="true">https://newbeelearn.com/tools/csvonline/</guid></item><item><title>2024 Aug Quick Summary</title><link>https://jodavaho.io/now/2024-aug.html</link><description>&lt;p&gt;I&amp;rsquo;m lucky to have found a new job that is essentially my dream position. I&amp;rsquo;m
now a full-time Rust programmer, but still working on a full planning stack.
It&amp;rsquo;s a very small startup, and I&amp;rsquo;m completely stoked.&lt;/p&gt;
&lt;p&gt;This summer has been beautiful, and we&amp;rsquo;ve been lucky to have a lot of time with family and friends.&lt;/p&gt;
&lt;p&gt;My only side project at the moment is a Cyberpunk-like rule system for a
tabletop RPG, which is heavily inspired by the old R Talsorian Cyberpunk 2020
rules, and is designed so that the mechanics directly affect roleplay and
player decisions, and any player decision should play out with the mechanics.&lt;/p&gt;</description><author>jodavaho.io</author><pubDate>Mon, 19 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jodavaho.io/now/2024-aug.html</guid></item><item><title>🧠 Dynamic Key/Value Pair Inputs in Phoenix LiveView Forms</title><link>https://james-carr.org/posts/2024-08-19-dynamic-keyvalue-pair-inputs-in-phoenix-liveview-forms/</link><description>While working on a new project recently, I needed to allow application users to enter a dynamic list of key-value pairs. Not knowing better, I reached for what I knew and built out a React component to dynamically add and remove inputs while also inserting into the websocket to sync the changeset with the server. Then on the server-side, I had to write some additional handlers to process those values. It worked, but it didn&amp;rsquo;t feel great.</description><author>James Carr</author><pubDate>Mon, 19 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://james-carr.org/posts/2024-08-19-dynamic-keyvalue-pair-inputs-in-phoenix-liveview-forms/</guid></item><item><title>Map Buddy Product Retrospective</title><link>https://thomashunter.name/posts/2024-08-19-map-buddy-product-retrospective-video</link><author>Thomas Hunter II</author><pubDate>Mon, 19 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://thomashunter.name/posts/2024-08-19-map-buddy-product-retrospective-video</guid></item><item><title>Never think about branch names again</title><link>https://maxleiter.com/notes/git-checkout-script</link><description>&lt;p&gt;I recommend swapping out `max` to basically anything else.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;  #!/bin/bash

  # Get the current month and day
  month=$(date +%m)
  day=$(date +%d)

  # Prefix the branch name with max/{month}-{day}
  branch_name=&amp;quot;max/$month-$day-$1&amp;quot;

  # Checkout the branch
  git checkout -b &amp;quot;$branch_name&amp;quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To use on macOS/linux, you can paste the above into a file in `/usr/local/bin` and make it executable with `chmod +x /usr/local/bin/gcb`.&lt;/p&gt;
&lt;p&gt;I also recommend setting your git settings to sort branches by `committerdate` so that you can easily find the branch you worked on most recently:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-bash"&gt;git config --global branch.sort committerdate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This does work with the default alphabetical sorting, but this works better if you work on features over long periods of time.&lt;/p&gt;</description><author>Max Leiter</author><pubDate>Mon, 19 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://maxleiter.com/notes/git-checkout-script</guid></item><item><title>Sometimes, I can't talk</title><link>https://ntietz.com/blog/sometimes-i-cant-talk/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Part of being a social animal is that we communicate with each other.
We live in a society, and we have to interact with other people.
A lot of that communication happens through language, especially spoken and signed language.&lt;/p&gt;
&lt;p&gt;For many of us, this language happens so freely and easily that it is as unremarkable as walking.
It's something that doesn't require a lot of effort or thought and happens without pushing through it.
But just as we sometimes &lt;em&gt;don't&lt;/em&gt; have the ability to freely walk, for some of us, some of the time, words don't come out so easily.&lt;/p&gt;
&lt;p&gt;There are some days where I don't have much capability to speak.&lt;/p&gt;
&lt;h1 id="usually-i-won-t-stop-talking"&gt;Usually, I won't stop talking&lt;/h1&gt;
&lt;p&gt;When I was a small child, I had trouble hearing.
This was related to chronic, severe ear infections.
I'd get them so badly&lt;sup class="footnote-reference" id="fr-my-last-one-1"&gt;&lt;a href="https://ntietz.com/blog/sometimes-i-cant-talk/#fn-my-last-one"&gt;[1]&lt;/a&gt;&lt;/sup&gt;, and was up many nights with severe pain.
Many of my preschool memories are of my mom rocking me in this one armchair we had in our living room, helping me get some comfort and maybe some sleep.
(I love you, mom. This memory is exactly the mom I aim to be with my own kids.)&lt;/p&gt;
&lt;p&gt;Since I had trouble hearing, I also had trouble learning how to speak.
I was probably producing the sounds I was hearing, but those weren't accurate sounds!
So when I repeated what I heard, it came out sorta garbled.
From what I've been told, I was a shy, quiet kid for a few years in elementary school.&lt;/p&gt;
&lt;p&gt;After my hearing was largely fixed and my ear infections slowed down significantly, my spoken language improved.
I had four or five years of speech therapy, which had some really frustrating moments (as any tough task does).
I remember this one time when I was learning the "th" sound and I just could &lt;em&gt;not&lt;/em&gt; hear the difference between what I was saying and what they were, but there was something!&lt;/p&gt;
&lt;p&gt;There was this one point in speech therapy where things clicked, and I suddenly got a lot more confident and free in my speech.
That was some time in fourth grade, after which the teachers remarked that it was like a spigot turned on and they couldn't get me to &lt;em&gt;stop&lt;/em&gt; talking.
I was so, so happy to be able to communicate and be understood.&lt;/p&gt;
&lt;p&gt;I really love talking.
It's taken me time to get past my &lt;em&gt;shyness&lt;/em&gt;, which still flares up, but quiet?
I'm anything but quiet.&lt;/p&gt;
&lt;h1 id="sometimes-speech-is-painful"&gt;Sometimes speech is painful&lt;/h1&gt;
&lt;p&gt;Despite &lt;em&gt;liking&lt;/em&gt; to talk, some days it's very, very difficult.
It's painful, really.
Not in a physical sense: there is nothing wrong with my vocal tract.
More in a psychological sense.&lt;/p&gt;
&lt;p&gt;This has been true as long as I can remember.
No one could really tell early on, because you can't tell if a small child has this difficulty when she's already having trouble &lt;em&gt;actually hearing&lt;/em&gt; and &lt;em&gt;actually speaking&lt;/em&gt; due to medical issues.
And later on, it seems like it's just a behavioral issue: the kid is just being stubborn, she's perfectly able to speak.
But it's not troublemaking, it's just that sometimes producing speech is really hard, painful, and draining.&lt;/p&gt;
&lt;p&gt;The best way I can describe it is that it's like walking on a floor covered in glue.
Normally, we can walk without any resistance.
But on days when I can't talk, the floor is just covered in glue.
It makes every single step unpleasant, one that you have to consider and force.
By the time you make it across the room, you're drained!
You probably don't even want to take a single step, once you know what you're in for, but you could if a life depended on it.&lt;/p&gt;
&lt;p&gt;It's like that with speech for me, those days.
It takes &lt;em&gt;so&lt;/em&gt; much effort to speak out loud.
But, I can push through that!
And so for years I &lt;em&gt;did&lt;/em&gt;, thinking this was just some resistance or stubbornness, or &lt;em&gt;laziness&lt;/em&gt;.
But really, it was legitimate, and not at all laziness.&lt;/p&gt;
&lt;p&gt;When I push through those times, I make the problem worse.
If I push and talk, more than a few words (sometimes even those few), I'll drain myself further and the episodes take much longer, are harder to recover from, and impact me in other areas of my life.&lt;/p&gt;
&lt;p&gt;I've not been able to figure out a specific trigger for these episodes.
That said, times of high stress and high emotion certainly seem to be likely to contribute to it.
If I'm out of my element, I'm more likely to have a meltdown, and inability to speak may be part of that meltdown for me.
I've noticed similar trends in a young relative of mine who has similar episodes.
My experience with it has helped me help them during those episodes, and come out of them faster, since I have a guess at what it feels like for them.&lt;/p&gt;
&lt;h1 id="how-we-deal-with-it"&gt;How we deal with it&lt;/h1&gt;
&lt;p&gt;This section was originally going to be titled "how I deal with it," but this really deserves "we," since communication is a group activity.
No solution works if those you're communicating with aren't onboard or will shame you for it.&lt;/p&gt;
&lt;p&gt;For a long time, the way I dealt with it was: "poorly."
(That joke was funny the &lt;em&gt;first&lt;/em&gt; time I told it in therapy.)
But over time, by letting go of some expectations on myself and drawing from the wealth of information and tools available from the broader neurodivergence community, I've gotten a few really good tools.
They don't "fix" me: they make it so that I &lt;em&gt;can communicate&lt;/em&gt;, and they remove the &lt;em&gt;pressure&lt;/em&gt; to speak.
They fill a similar role to my glasses.&lt;/p&gt;
&lt;p&gt;The key for my coping mechanisms here is that when I'm in my no-talking episodes, it's specific to &lt;em&gt;speech&lt;/em&gt;.
I am not hindered in written/typed language or other forms of communication.
Though, physical contact is also &lt;em&gt;usually&lt;/em&gt; unwelcome during these episodes, so some forms of communication, like light touch, are off the table.&lt;/p&gt;
&lt;p&gt;The first thing I tried is &lt;strong&gt;using text-to-speech&lt;/strong&gt;.
When I was feeling pretty good, I &lt;a href="https://support.apple.com/en-us/104993"&gt;trained the personal voice&lt;/a&gt; on my iPhone to use my voice.
Then, when I was not able to talk, I used text to speech on my phone, and it &lt;em&gt;sounded like me&lt;/em&gt;!
This works really well sometimes.
I am able to produce short phrases easily, and in a voice that's very much like my regular daily voice.&lt;/p&gt;
&lt;p&gt;The drawback here is that it takes time to type out what I want to say.
That lag time can be jarring in conversational flow, and it means speech doesn't start until I've typed the whole phrase, which is different from how we normally speak.
There are a lot of tools out there for this, which can speed it up!
I've not invested in those, because they're quite expensive and my episodes are infrequent enough right now that I can't justify it.
I'll revisit them if that ever changes.&lt;/p&gt;
&lt;p&gt;The other thing we do is &lt;strong&gt;I use written/typed text&lt;/strong&gt;.
I'm usually able to type or write, which means we can communicate without me having to speak!
Just like with text-to-speech, it takes time to type, but there are two advantages.
You can read the whole thing immediately upon receipt, and if I'm sitting with you, you can also read what I'm writing &lt;em&gt;as I type it&lt;/em&gt;, which makes conversation more natural.
Usually when I do this my conversation partner will speak out loud in response&lt;sup class="footnote-reference" id="fr-funny-texts-1"&gt;&lt;a href="https://ntietz.com/blog/sometimes-i-cant-talk/#fn-funny-texts"&gt;[2]&lt;/a&gt;&lt;/sup&gt;, so they don't have to change behavior besides reading what I say instead of listening for it.&lt;/p&gt;
&lt;p&gt;I also &lt;strong&gt;use chat for meetings&lt;/strong&gt;.
I tried to do text-to-speech but ended up having trouble finding adequate Linux tooling for it and realized I was just adding an extra layer without much benefit.
So instead, I use chat in meetings when I'd speak, and as long as people expect it it works well!
I have done this at work and with friends, and it's worked out quite well.&lt;/p&gt;
&lt;p&gt;One thing that has helped is having an advocate in these calls and meetings.
Someone who reminds people that I said something in chat if people don't notice it is really necessary until everyone gets used to it.
By now, I don't run into messages getting lost in these meetings, so I think everyone is adjusted.
Sometimes, my one-on-one meetings even just end up as bidirectional chat, if neither of us wants to do a call but both of us have a few small things to chat about.
Then this ends up benefiting &lt;em&gt;both&lt;/em&gt; of us!&lt;/p&gt;
&lt;h1 id="this-is-my-experience"&gt;This is &lt;em&gt;my&lt;/em&gt; experience&lt;/h1&gt;
&lt;p&gt;There are a lot of different ways that people can be non-verbal.
I'm writing this because I think that there are probably other people who have a similar experience to me.
I want this experience out there, so people can see it and relate to it, or get a lens into someone else's world.&lt;/p&gt;
&lt;p&gt;I felt encouraged to write this when I found out that talking about this was &lt;em&gt;already&lt;/em&gt; helpful to someone I'd shared this experience with.
She was able to relate my experience to someone else's experiences, and then help them figure out how to navigate the world a little better.&lt;/p&gt;
&lt;p&gt;Since it's my experience, other people won't have the exact same (or even similar) experiences!
But it can give you a starting point toward understanding them, through communication &lt;em&gt;with&lt;/em&gt; them, if that's helpful to you both.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Thank you to &lt;a href="https://erikarow.land/"&gt;Erika Rowland&lt;/a&gt; and &lt;a href="https://sokolskayatranslations.com/"&gt;Eugenia Tietz-Sokolskaya&lt;/a&gt; for feedback on a draft of this post.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-my-last-one"&gt;
&lt;p&gt;My last full-blown ear infection was in high school&lt;sup class="footnote-reference" id="fr-wow-im-old-1"&gt;&lt;a href="https://ntietz.com/blog/sometimes-i-cant-talk/#fn-wow-im-old"&gt;[3]&lt;/a&gt;&lt;/sup&gt;, and resulted in a ruptured ear drum.
I did regain full hearing in that ear afterwards.
I've had a couple of unpleasant episodes since then, but none quite so severe. &lt;a href="https://ntietz.com/blog/sometimes-i-cant-talk/#fr-my-last-one-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-funny-texts"&gt;
&lt;p&gt;This does make text/chat transcripts &lt;em&gt;really&lt;/em&gt; strange to look at later on, where it seems like I'm having a very one-sided conversation! &lt;a href="https://ntietz.com/blog/sometimes-i-cant-talk/#fr-funny-texts-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-wow-im-old"&gt;
&lt;p&gt;This is where I realize that this ear infection was... over half my life ago. &lt;a href="https://ntietz.com/blog/sometimes-i-cant-talk/#fr-wow-im-old-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 19 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/sometimes-i-cant-talk/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>To broadcast or not to broadcast</title><link>https://ochagavia.nl/blog/to-broadcast-or-not-to-broadcast/</link><description>One of my friends, an outstanding programmer who likes to fly under the radar, frequently tells me he admires my &amp;ldquo;ability to broadcast&amp;rdquo;. I guess he refers to the fact that I&amp;rsquo;m keeping a blog, and that I&amp;rsquo;m not afraid of writing with transparency about my adventures in software engineering. To me, it all feels natural, so his comment motivated me to explain in more detail why I&amp;rsquo;m doing what I&amp;rsquo;m doing.</description><author>Consulting on Adolfo Ochagavía</author><pubDate>Mon, 19 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ochagavia.nl/blog/to-broadcast-or-not-to-broadcast/</guid></item><item><title>Town Hall #24: FWD</title><link>https://taylor.town/town-hall-0024</link><description>why do they put "no trespassing" signs on all the places i want to go</description><author>taylor.town</author><pubDate>Mon, 19 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/town-hall-0024</guid></item><item><title>Fancy lightweight prompts for Eshell and Zsh</title><link>https://lambdaland.org/posts/2024-08-19_fancy_eshell_prompt/</link><description>&lt;p&gt;I started using the Zsh a few years ago and I&amp;rsquo;ve liked its completion features. I tried out Oh-my-zsh for a while and I liked the stock Robby Russel prompt. It gave me all the information I cared about: the status of the last command, current directory, and the state of the current Git repository.&lt;/p&gt;
&lt;p&gt;However, I didn&amp;rsquo;t like how slow Oh-my-zsh was making my shell startup. This mattered especially, I think, because my Emacs config would fire up a shell on startup to read the &lt;code&gt;ENV&lt;/code&gt; so it could configure some language servers properly. Irked at how long stuff was taking, I set out to build my own.&lt;/p&gt;
&lt;h2 id="fancy-zsh-prompt-no-extra-packages-needed"&gt;
  Fancy Zsh prompt, no extra packages needed
  &lt;a class="anchor" href="#fancy-zsh-prompt-no-extra-packages-needed"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the code for my Zsh prompt:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-zsh"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;# This is important to make some things play nicely with Emacs.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;# They're not critical to the shell prompt per se, but I think they're&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;# pretty useful.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;# Bail out of rest of setup if we're coming in from TRAMP&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1;"&gt;[[&lt;/span&gt; $TERM &lt;span style="color: #81a1c1;"&gt;==&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"dumb"&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;]]&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;&amp;amp;&amp;amp;&lt;/span&gt; unsetopt zle &lt;span style="color: #81a1c1;"&gt;&amp;amp;&amp;amp;&lt;/span&gt; PS1&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;'$ '&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1;"&gt;[&lt;/span&gt; -n &lt;span style="color: #a3be8c;"&gt;"&lt;/span&gt;$EAT_SHELL_INTEGRATION_DIR&lt;span style="color: #a3be8c;"&gt;"&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;]&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;source&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"&lt;/span&gt;$EAT_SHELL_INTEGRATION_DIR&lt;span style="color: #a3be8c;"&gt;/zsh"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;# This tells the shell to expand the call to $(git_prompt_info)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;setopt PROMPT_SUBST
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;# This is a function that gathers information about the current HEAD.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;# It will show the name of the branch if there is one, otherwise the&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;# short hash of the currently checked-out commit.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;git_prompt_info &lt;span style="color: #81a1c1;"&gt;()&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1;"&gt;local&lt;/span&gt; ref
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    ref&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;$(&lt;/span&gt;git symbolic-ref HEAD 2&amp;gt; /dev/null&lt;span style="color: #81a1c1; font-weight: bold;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;||&lt;/span&gt; ref&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;$(&lt;/span&gt;git rev-parse --short HEAD 2&amp;gt; /dev/null&lt;span style="color: #81a1c1; font-weight: bold;"&gt;)&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;||&lt;/span&gt; &lt;span style="color: #81a1c1; font-weight: bold;"&gt;return&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1;"&gt;local&lt;/span&gt; STATUS
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1;"&gt;local&lt;/span&gt; -a FLAGS
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    FLAGS&lt;span style="color: #81a1c1;"&gt;=(&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;'--porcelain'&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;[[&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;${&lt;/span&gt;DISABLE_UNTRACKED_FILES_DIRTY&lt;span style="color: #81a1c1; font-weight: bold;"&gt;:-&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;}&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;==&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"true"&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    FLAGS&lt;span style="color: #81a1c1;"&gt;+=&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;'--untracked-files=no'&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;case&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;${&lt;/span&gt;GIT_STATUS_IGNORE_SUBMODULES&lt;span style="color: #81a1c1; font-weight: bold;"&gt;:-&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;}&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"&lt;/span&gt; in
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1;"&gt;(&lt;/span&gt;git&lt;span style="color: #81a1c1;"&gt;)&lt;/span&gt;  &lt;span style="color: #eceff4;"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1;"&gt;(&lt;/span&gt;*&lt;span style="color: #81a1c1;"&gt;)&lt;/span&gt; FLAGS&lt;span style="color: #81a1c1;"&gt;+=&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"--ignore-submodules=&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;${&lt;/span&gt;GIT_STATUS_IGNORE_SUBMODULES&lt;span style="color: #81a1c1; font-weight: bold;"&gt;:-&lt;/span&gt;dirty&lt;span style="color: #a3be8c;"&gt;}&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"&lt;/span&gt;  &lt;span style="color: #eceff4;"&gt;;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;esac&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    STATUS&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;$(&lt;/span&gt;git status &lt;span style="color: #a3be8c;"&gt;${&lt;/span&gt;FLAGS&lt;span style="color: #a3be8c;"&gt;}&lt;/span&gt; 2&amp;gt; /dev/null &lt;span style="color: #eceff4;"&gt;|&lt;/span&gt; tail -n1&lt;span style="color: #81a1c1; font-weight: bold;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;[[&lt;/span&gt; -n $STATUS &lt;span style="color: #81a1c1;"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1;"&gt;echo&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;" %F{red}[%F{yellow}&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;${&lt;/span&gt;ref&lt;span style="color: #eceff4;"&gt;#refs/heads/&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;}&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;%F{red}]%f"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1;"&gt;echo&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;" %F{green}(%F{yellow}&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;${&lt;/span&gt;ref&lt;span style="color: #eceff4;"&gt;#refs/heads/&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;}&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;%F{green})%f"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #81a1c1; font-weight: bold;"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1;"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #616e87; font-style: italic;"&gt;# If I'm on my home machine, don't show the hostname in the prompt.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;if&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;[[&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;`&lt;/span&gt;hostname&lt;span style="color: #a3be8c;"&gt;`&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;~ ^my-home-machine.* &lt;span style="color: #81a1c1;"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    PROMPT&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"%(?:%F{green}➤:%F{red}!%?)%f %F{cyan}%~%f\$(git_prompt_info) %(!:# :)"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #616e87; font-style: italic;"&gt;# Add "%m" to print the short hostname on other servers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    PROMPT&lt;span style="color: #81a1c1;"&gt;=&lt;/span&gt;&lt;span style="color: #a3be8c;"&gt;"%(?:%F{green}➤:%F{red}!%?)%f %F{blue}%m%f:%F{cyan}%~%f\$(git_prompt_info) %(!:# :)"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #81a1c1; font-weight: bold;"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s what the shell looks like in a clean repository:&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://lambdaland.org/img/fancy_shells/clean.png" /&gt;
&lt;/figure&gt;

&lt;p&gt;And here&amp;rsquo;s what it looks like in a repository with some uncommitted changes:&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://lambdaland.org/img/fancy_shells/dirty.png" /&gt;
&lt;/figure&gt;

&lt;p&gt;The green prompt will change to a red &lt;code&gt;!&lt;/code&gt; and show the exit status of the last command if it was anything other than 0.&lt;/p&gt;
&lt;p&gt;This should run pretty quick. The Git functions take almost no time to run, and the rest is straight-line Zsh script. Using this I was able to stop using Oh-my-zsh and dramatically reduce my shell startup time.&lt;/p&gt;
&lt;h2 id="eshell-prompt"&gt;
  Eshell prompt
  &lt;a class="anchor" href="#eshell-prompt"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I recently started using &lt;a href="https://www.masteringemacs.org/article/complete-guide-mastering-eshell"&gt;Eshell&lt;/a&gt; in Emacs, and I wanted the same prompt there as I had in my terminal. Here&amp;rsquo;s how I got the same prompt, using some functions for the incomparable &lt;a href="https://magit.vc/"&gt;Magit&lt;/a&gt; Git porcelain.&lt;/p&gt;
&lt;p&gt;First, you need to define a function that generates the prompt for the current directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-emacs-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;defun&lt;/span&gt; fancy-shell &lt;span style="color: #eceff4;"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #a3be8c;"&gt;"A pretty shell with git status"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;  &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;let*&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;((&lt;/span&gt;cwd &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;abbreviate-file-name &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;eshell/pwd&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;ref &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;magit-get-shortname &lt;span style="color: #a3be8c;"&gt;"HEAD"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;stat &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;magit-file-status&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;x-stat eshell-last-command-status&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;         &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;git-chunk
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;          &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;if&lt;/span&gt; ref
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;              &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;format&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"%s%s%s "&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                      &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;propertize&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;if&lt;/span&gt; stat &lt;span style="color: #a3be8c;"&gt;"["&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"("&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;'font-lock-face&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;list&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;:foreground&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;if&lt;/span&gt; stat &lt;span style="color: #a3be8c;"&gt;"red"&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"green"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                      &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;propertize&lt;/span&gt; ref &lt;span style="color: #a3be8c;"&gt;'font-lock-face&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;:foreground&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"yellow"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;                      &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;propertize&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;if&lt;/span&gt; stat &lt;span style="color: #a3be8c;"&gt;"]"&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;")"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;'font-lock-face&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;list&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;:foreground&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;if&lt;/span&gt; stat &lt;span style="color: #a3be8c;"&gt;"red"&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"green"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;            &lt;span style="color: #a3be8c;"&gt;""&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;propertize&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;format&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"%s %s %s$ "&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;0&lt;/span&gt; x-stat&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;format&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;propertize&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"!%s"&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;'font-lock-face&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;:foreground&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"red"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt; x-stat&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;               &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;propertize&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"➤"&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;'font-lock-face&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;list&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;:foreground&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;if&lt;/span&gt; &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;&amp;lt;&lt;/span&gt; &lt;span style="color: #b48ead;"&gt;0&lt;/span&gt; x-stat&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"red"&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"green"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             &lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #88c0d0;"&gt;propertize&lt;/span&gt; cwd &lt;span style="color: #a3be8c;"&gt;'font-lock-face&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;&lt;span style="color: #81a1c1;"&gt;:foreground&lt;/span&gt; &lt;span style="color: #a3be8c;"&gt;"#45babf"&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;             git-chunk&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #a3be8c;"&gt;'read-only&lt;/span&gt; &lt;span style="color: #8fbcbb;"&gt;t&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #a3be8c;"&gt;'front-sticky&lt;/span&gt;   &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;font-lock-face read-only&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;     &lt;span style="color: #a3be8c;"&gt;'rear-nonsticky&lt;/span&gt; &lt;span style="color: #81a1c1;"&gt;'&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;font-lock-face read-only&lt;span style="color: #eceff4;"&gt;))))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Now that that function is defined, you can tell Eshell to use that to make the shell prompt. (It&amp;rsquo;s also good to set &lt;code&gt;eshell-prompt-regexp&lt;/code&gt; so it knows where the prompt begins and ends.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-emacs-lisp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt eshell-prompt-function &lt;span style="color: #a3be8c;"&gt;'fancy-shell&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt eshell-prompt-regexp &lt;span style="color: #a3be8c;"&gt;"^[^#$\n]* [$#] "&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #eceff4;"&gt;(&lt;/span&gt;setopt eshell-highlight-prompt &lt;span style="color: #8fbcbb;"&gt;nil&lt;/span&gt;&lt;span style="color: #eceff4;"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It looks basically the same as the Zsh prompt, except there&amp;rsquo;s always a &lt;code&gt;$&lt;/code&gt; character at the end of the prompt. This is just to make Emacs&amp;rsquo; prompt-parsing easier.&lt;/p&gt;
&lt;p&gt;If you are curious about using Eshell, you should use Eshell in concert with &lt;a href="https://codeberg.org/akib/emacs-eat"&gt;Eat&lt;/a&gt;, which runs commands in a little terminal emulator. This makes interactive programs like code REPLs or programs that use character escape codes work correctly. (You can even run &lt;code&gt;emacs -nw&lt;/code&gt; and have it work! Madness!) I can use Eshell for more than 90% of what I need to do in a shell now, and that&amp;rsquo;s pretty nice.&lt;/p&gt;</description><author>Ashton Wiersdorf on Lambda Land</author><pubDate>Mon, 19 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2024-08-19_fancy_eshell_prompt/</guid></item><item><title>Building Slint on macOS with the skia backend</title><link>https://porkrind.org/missives/building-slint-on-macos-with-the-skia-backend/</link><description>I got a weird error while trying to compile a slint program with the skia backend on my macOS machine: Compiling skia-bindings v0.75.0 error: failed to run custom build command for `skia-bindings v0.75.0` […a huge section of output snipped…] clang -MD -MF obj/third_party/externals/libpng/libpng.png.o.d -DPNG_SET_OPTION_SUPPORTED -DNDEBUG -w -Wno-attributes -ffp-contract=off -fstrict-aliasing -fPIC -fvisibility=hidden -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.5.sdk -target arm64-apple-macos11 &amp;#8230; &lt;a class="more-link" href="https://porkrind.org/missives/building-slint-on-macos-with-the-skia-backend/"&gt;Continue reading &lt;span class="screen-reader-text"&gt;Building Slint on macOS with the skia backend&lt;/span&gt;&lt;/a&gt;</description><author>Missives</author><pubDate>Sun, 18 Aug 2024 22:35:14 GMT</pubDate><guid isPermaLink="true">https://porkrind.org/missives/building-slint-on-macos-with-the-skia-backend/</guid></item><item><title>scrapsheets (demo)</title><link>https://taylor.town/live-2024-cfp</link><description>weird spreadsheet program that barely works</description><author>taylor.town</author><pubDate>Sun, 18 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/live-2024-cfp</guid></item><item><title>Effective Changelogs</title><link>https://xavd.id/blog/post/effective-changelogs/</link><description>Tips and tricks to write a changelog worth reading.&lt;br /&gt;&lt;br /&gt;&lt;a href="https://xavd.id/blog/post/effective-changelogs/"&gt;Read the whole thing&lt;/a&gt;.</description><author>The David Brownman Blog</author><pubDate>Sun, 18 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xavd.id/blog/post/effective-changelogs/</guid></item><item><title>Quantum Entanglement, Locality, Determinism</title><link>https://bytepawn.com/quantum-entanglement-locality-determinism.html</link><description>&lt;p&gt;This blog post explores foundational quantum mechanics concepts such as superposition, entanglement, and the challenges posed by locality and determinism, while highlighting the historical efforts of Einstein and Bohm to reconcile these phenomena with hidden variable theories.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Double slit experiment" src="https://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/Double-slit.svg/800px-Double-slit.svg.png" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Sun, 18 Aug 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/quantum-entanglement-locality-determinism.html</guid></item><item><title>Master Direnv: Guide to Managing Environment Variables Efficiently</title><link>https://themythicalengineer.com/from-chaos-to-control-using-direnv-to-manage-multiple-github-accounts.html</link><description>Discover how to use direnv to seamlessly switch between multiple GitHub accounts, automate git configuration, and eliminate manual setup. Step-by-step tutorial for developers.</description><author>The Mythical Engineer</author><pubDate>Sat, 17 Aug 2024 09:52:00 GMT</pubDate><guid isPermaLink="true">https://themythicalengineer.com/from-chaos-to-control-using-direnv-to-manage-multiple-github-accounts.html</guid></item><item><title>La manipulation de codes QR via CLI</title><link>https://ache.one/notes/manipuler-des-codes-qr-en-cli</link><description>L'usage des codes QR (traduction de QR Codes) s'étant démocratisé, j'ai eu besoin d'outils pour encoder et décoder des codes QR.
Je vous présente les outils que j'utilise pour manipuler les code QR ainsi que les quelques cas d'utilisation courante de code QR.</description><author>ache: Blog personnel</author><pubDate>Sat, 17 Aug 2024 07:09:04 GMT</pubDate><guid isPermaLink="true">https://ache.one/notes/manipuler-des-codes-qr-en-cli</guid></item><item><title>Pocket Network Shannon Update — Alpha TestNet #3</title><link>https://olshansky.info/posts/pocket-network-shannon-update-alpha-testnet-3/</link><description>Enable Gateways by streamlining access to a decentralized supply network 🌿</description><author>🦉 olshansky 🦁</author><pubDate>Sat, 17 Aug 2024 03:51:07 GMT</pubDate><guid isPermaLink="true">https://olshansky.info/posts/pocket-network-shannon-update-alpha-testnet-3/</guid></item><item><title>MicroGrad.jl: Part 4 Extensions</title><link>https://liorsinai.github.io/machine-learning/2024/08/17/micrograd-4-ext.html</link><description>A series on automatic differentiation in Julia. Part 4 extends part 3 to handle maps, getfield and anonymous functions. It creates a generic gradient descent and uses this to fit a polynomial.</description><author>Lior Sinai</author><pubDate>Sat, 17 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://liorsinai.github.io/machine-learning/2024/08/17/micrograd-4-ext.html</guid></item><item><title>MicroGrad.jl: Part 3 Automation with IRTools</title><link>https://liorsinai.github.io/machine-learning/2024/08/10/micrograd-3-ir.html</link><description>A series on automatic differentiation in Julia. Part 3 uses metaprogramming based on IRTools.jl to generate a modified (primal) forward pass and to reverse differentiate it into a backward pass. This is a more robust approach than the expression based approach in Part 2.</description><author>Lior Sinai</author><pubDate>Sat, 17 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://liorsinai.github.io/machine-learning/2024/08/10/micrograd-3-ir.html</guid></item><item><title>The Silicon Valley Tarpits</title><link>https://taylor.town/tarpits</link><description>Daddy, how did the cofounders fall into these tarpits?</description><author>taylor.town</author><pubDate>Sat, 17 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/tarpits</guid></item><item><title>Old Computer Challenge v4: Conclusion</title><link>https://www.rollc.at/posts/2024-08-17-old-computer-challenge-v4-conclusion/</link><description>&lt;p&gt;So I&amp;rsquo;ve participated in &lt;a href="https://dataswamp.org/~solene/2024-06-24-old-computer-challenge-v4-announce.html"&gt;OCC v4&lt;/a&gt; with my &lt;a href="https://www.rollc.at/posts/2024-07-02-tibook/"&gt;TiBook&lt;/a&gt;. Just like
&lt;a href="https://www.rollc.at/posts/2022-07-17-tocc2-conclusion/"&gt;last time&lt;/a&gt;, I have some conclusions.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Conclusion&lt;/em&gt; (n.) - when you&amp;rsquo;re done thinking.&lt;/p&gt;&lt;/blockquote&gt;</description><author>rollcat's website</author><pubDate>Sat, 17 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.rollc.at/posts/2024-08-17-old-computer-challenge-v4-conclusion/</guid></item><item><title>Jetpack compose navigation</title><link>https://whackylabs.com/kotlin/compose/navigation/2024/08/16/jetpack-compose-navigation/</link><description>&lt;p&gt;It’s Friday night! And you know what that means right? It’s time to dig deeper into Jetpack Compose. And today I would like to try out the navigation between screens with Jetpack Compose.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgflip.com/i/90jgo1"&gt;&lt;img src="https://i.imgflip.com/90jgo1.jpg" title="made at imgflip.com" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How hard can it?&lt;/p&gt;

&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;
&lt;p&gt;Installing dependencies is never fun in an Android project.
First you need to find the &lt;code class="language-plaintext highlighter-rouge"&gt;libs.version.toml&lt;/code&gt; file and add these lines:&lt;/p&gt;

&lt;div class="language-toml highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nn"&gt;[version]&lt;/span&gt;
&lt;span class="py"&gt;androidx-navigation&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"2.7.7"&lt;/span&gt;
&lt;span class="py"&gt;kotlinxSerializationJson&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"1.6.3"&lt;/span&gt;

&lt;span class="nn"&gt;[libraries]&lt;/span&gt;
&lt;span class="nn"&gt;androidx-navigation-compose&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"androidx.navigation:navigation-compose"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"androidx-navigation"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nn"&gt;kotlinx-serialization-json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;module&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.jetbrains.kotlinx:kotlinx-serialization-json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"kotlinxSerializationJson"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nn"&gt;[plugins]&lt;/span&gt;
&lt;span class="nn"&gt;kotlin-serialization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="py"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"org.jetbrains.kotlin.plugin.serialization"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="py"&gt;version.ref&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"kotlin"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Run &lt;code class="language-plaintext highlighter-rouge"&gt;gradle sync&lt;/code&gt;. Then open the &lt;code class="language-plaintext highlighter-rouge"&gt;build.gradle.kts&lt;/code&gt; within the &lt;strong&gt;app/&lt;/strong&gt; and apply the plugin and add the dependency&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;plugins {
  // ...
  alias(libs.plugins.kotlin.serialization)
}

dependencies {
  // ...
  implementation(libs.androidx.navigation.compose)
  implementation(libs.kotlinx.serialization.json)
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;And then do another round of &lt;code class="language-plaintext highlighter-rouge"&gt;gradle sync&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If all went fine so far, then you’re all set for writing the code. If not, delete your project and start from scratch.&lt;/p&gt;

&lt;h3 id="implementation"&gt;Implementation&lt;/h3&gt;

&lt;p&gt;Navigation in Android works with the help of these 2 components:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;NavController&lt;/code&gt;: Top level object to navigate between screens&lt;/li&gt;
  &lt;li&gt;&lt;code class="language-plaintext highlighter-rouge"&gt;NavHost&lt;/code&gt;: Container within which all routing happens.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So to make this work, we first need to inject the &lt;code class="language-plaintext highlighter-rouge"&gt;NavController&lt;/code&gt; at the root level of the app. Like say the &lt;code class="language-plaintext highlighter-rouge"&gt;MainActivity&lt;/code&gt; or the root composable like &lt;code class="language-plaintext highlighter-rouge"&gt;PhotoApp&lt;/code&gt; in our case.&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;PhotoApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;NavHostController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rememberNavController&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;..&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then simply setup all the available routes&lt;/p&gt;

&lt;div class="language-kotlin highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nc"&gt;NavHost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;navController&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;startDestination&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"home"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"home"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;HomeScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;onSelectPhoto&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;navController&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;navigate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"photos/${it.id}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nf"&gt;composable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"photos/{id}"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;DetailScreen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="o"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;getString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And that is all there to it.&lt;/p&gt;

&lt;h3 id="thoughts"&gt;Thoughts&lt;/h3&gt;
&lt;p&gt;Now of course, one can go crazy with typed routing with sealed classes and what not. But this is the gist of it.&lt;/p&gt;

&lt;p&gt;Just like React Native, the path arguments are by default string types and if there’s more data to be passed between the screens we need to come up with our strategy.&lt;/p&gt;

&lt;p&gt;The good thing is the the back button comes for free with Android OS. But for custom back button we again need to devise our own tooling.&lt;/p&gt;

&lt;p&gt;So there is how you navigate between screens with Android. The code for this experiment is up there sitting in the cloud nicely with the rest of the PhotoApp.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/chunkyguy/PhotoApp/tree/master/kotlin"&gt;https://github.com/chunkyguy/PhotoApp/tree/master/kotlin&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="references"&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://developer.android.com/develop/ui/compose/navigation"&gt;Navigation with Compose&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://developer.android.com/codelabs/basic-android-kotlin-compose-navigation"&gt;Navigate between screens with Compose&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=AIC_OFQ1r3k&amp;amp;t=141s"&gt;Type-Safe Navigation with the OFFICIAL Compose Navigation Library&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://developer.android.com/guide/navigation"&gt;Navigation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=glyqjzkc4fk"&gt;Navigation Basics in Jetpack Compose&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://www.youtube.com/watch?v=4gUeyNkGE3g"&gt;Jetpack Compose Navigation for Beginners - Android Studio Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Sat, 17 Aug 2024 02:20:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/kotlin/compose/navigation/2024/08/16/jetpack-compose-navigation/</guid></item><item><title>VLMs Hallucinate</title><link>https://www.danielcorin.com/posts/2024/vlms-hallucinate/</link><description>VLMs Hallucinate</description><author>Thought Eddies</author><pubDate>Sat, 17 Aug 2024 01:44:57 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2024/vlms-hallucinate/</guid></item><item><title>If One Thing is Infinite, Everything is Infinite</title><link>https://somethingdecent.co.uk/opinions-blogs/if-one-thing-is-infinite-everything-is-infinite/</link><description>&lt;p&gt;Last night I found myself sitting at my local viewpoint watching the glittering lights of London flicker against the backdrop of a night sky. As I sat there watching Wembley Stadium&amp;#8217;s arch gradually change colour from white to blue and numerous commercial aircraft glide into Heathrow Airport I set off down one of many rabbit [&amp;#8230;]&lt;/p&gt;
&lt;p&gt;The post &lt;a href="https://somethingdecent.co.uk/opinions-blogs/if-one-thing-is-infinite-everything-is-infinite/" rel="nofollow"&gt;If One Thing is Infinite, Everything is Infinite&lt;/a&gt; appeared first on &lt;a href="https://somethingdecent.co.uk" rel="nofollow"&gt;Something Decent&lt;/a&gt;.&lt;/p&gt;</description><author>Something Decent</author><pubDate>Fri, 16 Aug 2024 13:40:36 GMT</pubDate><guid isPermaLink="true">https://somethingdecent.co.uk/opinions-blogs/if-one-thing-is-infinite-everything-is-infinite/</guid></item><item><title>Writer’s Block</title><link>https://blog.adnansiddiqi.me/writers-block/</link><description>&lt;p&gt;There was a time when I effortlessly wrote multiple blog posts each month, a practice that continued for years. However, for the last two years, I&amp;#8217;ve been struggling to write anything. It&amp;#8217;s not that I haven&amp;#8217;t had anything to write about—look, this blog is a platform where I share whatever I learn. I&amp;#8217;ve written on various topics in the past. It&amp;#8217;s not like I haven&amp;#8217;t done anything new in the last two years; I just haven&amp;#8217;t felt the urge to write about them or convert my learnings into blog posts. This eventually made my learning activities somewhat stagnant. On the other hand, I became so occupied with work for others (job and clients) that I didn&amp;#8217;t realize I was neglecting something that had helped me land jobs, get clients, gain praise from fellow developers worldwide, and build credibility. I shouldn&amp;#8217;t have let this happen, but it was unintentional—perhaps a form of writer&amp;#8217;s block? Maybe Seth is right here? It&amp;#8217;s not that I haven&amp;#8217;t produced anything; I did write a few things here and there, including some filler articles just to keep my blog relevant to Google. But I wasn&amp;#8217;t satisfied; I wasn&amp;#8217;t enjoying it. Anyway, over the last two years, I&amp;#8217;ve been working on two to three major projects: trading automation, GenAI/LLM, and chatbots. As always, I don&amp;#8217;t claim to be an expert in any of them. Speaking of trading, I mostly implemented strategies developed by others—they dictated their strategies to me, and I converted them into code. Moving forward, I&amp;#8217;ve decided to focus on these two major areas for now. I could certainly write about other topics, but the main theme will revolve around these two areas. Since I couldn&amp;#8217;t find the exact material I needed to learn, I&amp;#8217;ve decided to write in my own style. Stay tuned! Image Created via chatGPT If you like this post then you should subscribe to my blog for future updates. * indicates required Email Address *&lt;/p&gt;
The post &lt;a href="https://blog.adnansiddiqi.me/writers-block/"&gt;Writer’s Block&lt;/a&gt; first appeared on &lt;a href="https://blog.adnansiddiqi.me"&gt;Adnan's Random bytes&lt;/a&gt;.</description><author>Adnan's Random bytes</author><pubDate>Fri, 16 Aug 2024 13:28:07 GMT</pubDate><guid isPermaLink="true">https://blog.adnansiddiqi.me/writers-block/</guid></item><item><title>Color.lol: AI-generated coloring pages</title><link>https://shruggingface.com/microblog/2024/08/16/color-lol-ai-generated-coloring-pages</link><description>I recently started collaborating with Jessica on a new project we’ve lovingly named Color.lol

Color lets you create custom printable coloring pages that are fun for all ages. It’s got two unique prompting modes we’ve dubbed WordMode™️ and KidMode™️.</description><author>shruggingface.com</author><pubDate>Fri, 16 Aug 2024 07:52:00 GMT</pubDate><guid isPermaLink="true">https://shruggingface.com/microblog/2024/08/16/color-lol-ai-generated-coloring-pages</guid></item><item><title>How I ended up working as a software developer</title><link>https://ounapuu.ee/posts/2024/08/16/career/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/08/16/career/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;I&amp;rsquo;ve officially worked as a software developer since August 2016, and by now I
have a fair share of stories to tell from those years. But those are stories for
another time.&lt;/p&gt;
&lt;p&gt;Today I&amp;rsquo;d like to focus on where it all got started.&lt;/p&gt;
&lt;h3 id="the-early-days"&gt;The early days&lt;/h3&gt;
&lt;p&gt;I never considered myself good with computers, or a nerd, or anything like that
during my childhood. All my computing experiences can be summed up in a pretty
short list, and most of the memories are around computer games.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/petthekitty.jpg"&gt;
        &lt;img alt="I was simply too busy petting the kitty." height="800" src="https://ounapuu.ee/posts/2024/08/16/career/media/petthekitty_hu_ca5a9763324e17c6.jpg" style="width: auto; height: auto; border-radius: 8px;" width="1184" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      I was simply too busy petting the kitty.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Starting off, there was that one Windows 95 box with no internet connection at
home. Me and my younger brother defaced the startup screen in Paint because it
was just one of the images on the drive, and there wasn&amp;rsquo;t anything else
interesting
for us to do there.&lt;/p&gt;
&lt;p&gt;Then there was that one laptop the family had temporarily. It ran Windows XP,
which felt really modern because the user interface had actual colors.&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; I
remember
being fascinated by one game on
it: &lt;a href="https://www.myabandonware.com/game/siia-sinna-laebi-linna-s-e32"&gt;Siia sinna läbi linna.&lt;/a&gt;
It was a simple educational game where you had to follow traffic rules as a
pedestrian
and walk around. I had the most fun with it when I tried to illegally cross the
road and barely miss the cars.&lt;/p&gt;
&lt;p&gt;My aunt had a desktop PC that I rarely could play with, but it had some great
games, like The Need for Speed (the very first one!) and a PC port of Sonic the Hedgehog 3.&lt;/p&gt;
&lt;p&gt;Later on there was a Compaq Armada 1592DT. It ran &lt;a href="https://en.wikipedia.org/wiki/Windows_Me"&gt;Windows Millenium Edition,&lt;/a&gt;
which is commonly regarded as &lt;strong&gt;the worst&lt;/strong&gt; Windows release ever. It was not great, I can tell you
that.&lt;/p&gt;
&lt;p&gt;It was around this time that I also got into gaming more. I played through the
demos
of Need for Speed III Hot Pursuit and Sports Car GT&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; &lt;em&gt;&lt;strong&gt;hundreds&lt;/strong&gt;&lt;/em&gt; of times.
Free demos
was all I got, and not many games even ran on the laptop. The two games in
question
also ran slowly, but I still loved them.&lt;/p&gt;
&lt;p&gt;Then there was a Windows 98 desktop PC that I got around 2004-2005. This time
in my life was characterized by family drama, so it was great to have a place
to escape. My fondest memories include &lt;em&gt;finally&lt;/em&gt; playing the full version of
Need for
Speed III Hot Pursuit, and hours and hours of RuneScape.&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt; I still remember one
random event occurring in RuneScape where a tree pops up and you have to
interact
with them or try to kill them, however my computer froze at the time and no
amount
of hitting it with my foot helped. Lost my full mithril armor that day. The
computer eventually gave up with what I
assume was a hard drive failure.&lt;/p&gt;
&lt;p&gt;Then we got an actual new computer for the first time in my life. It was 2006
and we got a Fujitsu tower PC with an AMD Athlon 64 X2 4200+ and an Nvidia
GeForce 7300. The GPU was passively
cooled and died soon, and the warranty service put in a Nvidia GeForce 6500
instead.
That ran &lt;em&gt;much&lt;/em&gt; better. This was the era of more unpleasant life events, working
summers
as a newspaper seller&lt;sup id="fnref:4"&gt;&lt;a class="footnote-ref" href="#fn:4"&gt;4&lt;/a&gt;&lt;/sup&gt;, and playing a lot of GTA San Andreas and Need for
Speed World.
I could spend 9 hours playing every day while still keeping my grades up at
school.&lt;/p&gt;
&lt;p&gt;I had the time of my life playing games, and it&amp;rsquo;s probably what saved me from
making stupid, irreversible decisions.&lt;/p&gt;
&lt;h3 id="school-computers-and-me"&gt;School, computers and me&lt;/h3&gt;
&lt;p&gt;I went to first grade in 2002.&lt;/p&gt;
&lt;p&gt;The first time I could use a computer in a classroom was in &lt;strong&gt;2010.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;There was one classroom full of Windows XP boxes, and with something like 256 MB of RAM in them.
The UI was very gray, likely as a result of switching to a &amp;ldquo;classic&amp;rdquo; theme to
save some resources.&lt;/p&gt;
&lt;p&gt;I remember two items from the curriculum:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;creating a Word document&lt;/li&gt;
&lt;li&gt;creating some pixel art in Paint&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We had to remember which computer we used and hide our files somewhere in the
folder structure if we didn&amp;rsquo;t want to lose it between classes. USB sticks
were very expensive and not that popular as well.&lt;/p&gt;
&lt;p&gt;Our teacher was what you&amp;rsquo;d think of when you thought of the most stereotypical
sysadmin: probably good at their day job, but maybe not the best teacher. At least
they did show us the insides of a PC, and I remember how they were raving about
their IBM ThinkPad T20-series laptop and how the new ones were trash. I guess some
things never change.&lt;/p&gt;
&lt;p&gt;There was also a short robotics course where we could do things with LEGO
Mindstorms robots, but we never quite understood what we were doing.&lt;/p&gt;
&lt;p&gt;Not all schools in Estonia are made equal, and I experienced it first-hand. My
next school was a completely different experience, as they had &lt;em&gt;two&lt;/em&gt; computer classes, and
the machines they were replacing were still years ahead of what we had at the
previous school.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;m very happy to see that we have companies like &lt;a href="https://greendice.com/projects/"&gt;GreenDice&lt;/a&gt;
who are motivated by similar experiences and want to make sure that everyone
has access to computers. Most media consumption happens on phones, but the real
work still gets done on PC-s.&lt;/p&gt;
&lt;h3 id="the-part-where-i-started-programming"&gt;The part where I started programming&lt;/h3&gt;
&lt;p&gt;When I went to 10th grade, I did so at a new school. It is considered one of the &amp;ldquo;elite&amp;rdquo; ones in Estonia,
and I got there by pure accident.&lt;sup id="fnref:5"&gt;&lt;a class="footnote-ref" href="#fn:5"&gt;5&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Ah, 2011. The iPhone was new, smartphones were evolving fast and I definitely
did not know how to talk to girls.&lt;/p&gt;
&lt;p&gt;Around 2012, our class teacher sent out a notice saying that Tartu University
was offering a free extracurricular course named &lt;em&gt;&amp;ldquo;Teeme ise arvutimänge&amp;rdquo;&lt;/em&gt;
(roughly translates to &amp;ldquo;Let&amp;rsquo;s build computer games&amp;rdquo;).
It was fully online with no scheduled mandatory hours and I liked games, so I
signed up.&lt;/p&gt;
&lt;p&gt;The course was about 7 weeks long. Every week you&amp;rsquo;d focus on one area, starting
with the basics of Python 3, building up your knowledge with more complex
parts of the language and creating a text-based game. At the end of the course
you had the choice of building a text-based game or a 2D game
with &lt;a href="https://www.pygame.org/"&gt;Pygame.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I had discovered retro gaming around this time, so I went ahead and
recreated the final boss level of Sonic the Hedgehog 3. I &amp;ldquo;borrowed&amp;rdquo; sprites and
the official soundtrack from various places online, and at the end I had
something
that didn&amp;rsquo;t run very well, but it &lt;em&gt;ran&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;And it still runs on my Fedora Linux 40 laptop!&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/game-level1.png"&gt;
        &lt;img alt="I got a bit excited replaying this." height="800" src="https://ounapuu.ee/posts/2024/08/16/career/media/game-level1_hu_c7280d5bb32f87bb.png" style="width: auto; height: auto; border-radius: 8px;" width="1026" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      I got a bit excited replaying this.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/game-title.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/08/16/career/media/game-title_hu_8f309b9b23055fd1.png"
             width="1026"
             height="800"
             alt=""original content do not steal""
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      "original content do not steal"
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;After the final project was completed, everyone who participated shared their
games with the group. I liked seeing a few games there where it was obvious
that the author had put actual effort in and loved working on it.&lt;/p&gt;
&lt;p&gt;I passed, in spite of the obscene number of copyright violations that I had committed.&lt;/p&gt;







  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/whatthehellisgit.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/08/16/career/media/whatthehellisgit_hu_8af0d2dc91f474b7.png"
             width="378"
             height="223"
             alt="It's clear that git was not part of the course."
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      It's clear that git was not part of the course.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Before I went to university, I also attended a one-off extracurricular
programming
class offered by the school. During that time, I showed my game to a classmate
there
and they were absolutely horrified at the code. For good reason. Whatever I did
there, it was horribly inefficient. At least my modern CPU can now chomp
through all that inefficiency.&lt;/p&gt;
&lt;p&gt;With that feedback in mind, I rewrote the game from scratch, made fewer stupid
mistakes
and added new features as well. It was still around the same concept of the
Sonic the Hedgehog 3 final boss level, but the obstacles and enemies were more
varied
and the game ran at 60 FPS even on an old laptop.&lt;/p&gt;
&lt;p&gt;What&amp;rsquo;s funny is that the &lt;em&gt;new&lt;/em&gt; version of the game doesn&amp;rsquo;t run. I had to
manually
set the resolution of the game, and even after that the game crashes randomly
after 5-10 seconds.&lt;/p&gt;







  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-crash.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-crash_hu_7b88764a3e12e6df.png"
             width="1200"
             height="750"
             alt="Definitely not future-proof."
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Definitely not future-proof.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;But hey, it looks so much better!&lt;/p&gt;







  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-title.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-title_hu_55dedeafb17f59a9.png"
             width="1200"
             height="750"
             alt="I even commissioned a drawing for the title screen. Big budget stuff!"
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      I even commissioned a drawing for the title screen. Big budget stuff!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-levelselect.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-levelselect_hu_2e4b5a6878983dcc.png"
             width="1200"
             height="750"
             alt="Ain't nobody got time for replaying the whole game to debug the final boss!"
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Ain't nobody got time for replaying the whole game to debug the final boss!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-level1.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-level1_hu_b6837e3096e44040.png"
             width="1200"
             height="750"
             alt="The sprites are animated now!"
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      The sprites are animated now!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-boss1.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-boss1_hu_eeaa5e3c56f06843.png"
             width="1200"
             height="750"
             alt="Variety in bosses!"
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Variety in bosses!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-boss2.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-boss2_hu_a24bf6896d4c698e.png"
             width="1200"
             height="750"
             alt="A bit harder to hit now."
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      A bit harder to hit now.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center" &gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-finalboss.png"&gt;
        &lt;img style="max-width: 100%; width: auto; height: auto; border-radius: 8px;"
             src="https://ounapuu.ee/posts/2024/08/16/career/media/newgame-finalboss_hu_1485bd6c78cc0255.png"
             width="1200"
             height="750"
             alt="The orange ones follow a sine wave pattern. Very advanced AI!"
        &gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      The orange ones follow a sine wave pattern. Very advanced AI!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;This Pygame adventure also spawned the only two StackOverflow questions that I
have ever
asked: &lt;a href="https://stackoverflow.com/questions/23571956/pygame-way-to-create-more-userevent-type-events"&gt;the one where I ran into limitations of the library,&lt;/a&gt;
and another
one &lt;a href="https://stackoverflow.com/questions/22287975/background-in-pygame-causes-graphical-issues"&gt;where I couldn&amp;rsquo;t understand why my background was funny.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I also ended up attending an event&lt;sup id="fnref:6"&gt;&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref"&gt;6&lt;/a&gt;&lt;/sup&gt; where I could show my game to others, and
&lt;a href="https://level1.ee/2014/06/eesti-indie-mangud-mangutisel-doomsday-zone/"&gt;the article about it is still up!&lt;/a&gt;
You might need to use your favourite translation service to understand it
though.&lt;/p&gt;
&lt;p&gt;This one course is the sole reason I chose computer science in university and
ended up as a software developer. This sequence of events is purely accidental,
and yet it sparked this fire in me that thrives on building new things and
troubleshooting issues. I &lt;em&gt;&lt;strong&gt;loved&lt;/strong&gt;&lt;/em&gt; the immediate visual feedback that I got
when
building the game and had a lot of fun trying to figure out how to make the
computer do what I want.&lt;/p&gt;
&lt;p&gt;A lot of what made me love programming was also what I enjoyed during my first
actual job as a software developer. I started out as a front-end developer,
working with Angular 2 right when it got the first official stable release.
It wasn&amp;rsquo;t easy to start with something like that as a junior developer, but
I loved the immediate visual feedback and learning how to use the browser
tooling to troubleshoot issues.&lt;/p&gt;
&lt;p&gt;For a few years I also considered pursuing a career in game development. I love
playing games, I love programming, so it would have made perfect sense, right?&lt;/p&gt;
&lt;p&gt;Unfortunately the only things I kept hearing about game development were
negative ones, involving
poor working conditions, &amp;ldquo;crunch time&amp;rdquo;, and how most game developers end up
with mental illnesses and severe burnout, all because some people in suits
want to make even more money.&lt;/p&gt;
&lt;h3 id="things-i-wish-i-knew"&gt;Things I wish I knew&lt;/h3&gt;
&lt;p&gt;The lead-up to my first actual job as a software developer included a lot of unknowns, anxiety and comparisons to more
successful students, which is why I&amp;rsquo;d like to share some tech tips for those just starting out in this field or IT in
general.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It&amp;rsquo;s OK to try out this role and end up deciding that it&amp;rsquo;s not for you.&lt;/strong&gt; I
know quite a few
people that started out as software developers, but ended up transitioning into
a different role that suited their interests better, such as team lead, product
manager or data engineering. Even I had a two-year gig as a team lead! Change
can be scary, but it might end up being the right thing to do.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don&amp;rsquo;t feel pressured to do &lt;em&gt;anything.&lt;/em&gt;&lt;/strong&gt; Some YouTuber just posted a video
that you &lt;em&gt;have&lt;/em&gt; to learn this new framework or programming language or you will never get
a job? Someone on Twitter keeps insisting that the blockchain is the future and everything
else is now obsolete? That is pure grade-A clickbaity bullcrap that plays on the fear of
missing out. Don&amp;rsquo;t fall for it.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also a subset of developers who have an expectation that you also write
code in your free time and regularly contribute to open source projects. Unless
you
want to work at Google, Meta or any of the other &amp;ldquo;big tech&amp;rdquo; companies, then
you really don&amp;rsquo;t need to cave in to this unreasonable pressure. You&amp;rsquo;ll be fine.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do things because you &lt;em&gt;love&lt;/em&gt; to do them.&lt;/strong&gt;  I have the opportunity to do
software development stuff 32 hours a week&lt;sup id="fnref:7"&gt;&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref"&gt;7&lt;/a&gt;&lt;/sup&gt;, why would I want to do even &lt;em&gt;more&lt;/em&gt; of it?
In my free time I want to do dumb experiments with my hardware and try out new ideas in my homelab. I also like writing a lot, so that&amp;rsquo;s what I end up doing.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Take care of yourself&lt;/strong&gt;, and learn about the symptoms of burnout. It&amp;rsquo;s OK to
take a rest if you need it. I wish someone told me this while I was in
university,
would have prevented quite a few chronic health issues.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Programming skills don&amp;rsquo;t matter as much as think they do.&lt;/strong&gt; They still
matter, don&amp;rsquo;t get me wrong, but it&amp;rsquo;s a relatively small part of the job. The
ability to work well with others and a problem-solving mindset will take you
very far, and you&amp;rsquo;ll figure out the technical details along the way.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Don&amp;rsquo;t do it for the money.&lt;/strong&gt; The money won&amp;rsquo;t cover the therapy that you&amp;rsquo;re
going
to need to get over the soul-crushing agony that you experience every day if you
secretly hate software development and everything around it. There&amp;rsquo;s a lot to dislike
in the industry even if you like this role, so I can&amp;rsquo;t imagine how bad it might
be as someone who isn&amp;rsquo;t able to enjoy the good parts of it.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ounapuu.ee/misc/good-reads/"&gt;I also have a list of good articles&lt;/a&gt; that will &lt;em&gt;hopefully&lt;/em&gt;
give you a better idea about the industry, the role and the expectations to a
software developer.&lt;/p&gt;
&lt;p&gt;Everyone has their own path to becoming a software developer, and this one
is mine. Yours will probably be different, and that is perfectly fine.&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;if you only knew grayscale interfaces all your life, then you&amp;rsquo;d be excited
about a change like that as well.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Sports Car GT launcher also went from reporting 1 MB of VRAM, to -1 MB, to
-1535 MB. For some reason it&amp;rsquo;s a core memory of mine. I don&amp;rsquo;t know why, either.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;it&amp;rsquo;s called Old-School RuneScape now. I&amp;rsquo;m not old, you&amp;rsquo;re old!&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:4"&gt;
&lt;p&gt;and I was damn good at it, too.&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:5"&gt;
&lt;p&gt;it&amp;rsquo;s not a weird flex, I just never planned on switching schools, but the
grades were good and the toilets in the new school weren&amp;rsquo;t thick with smoke, so I
switched.&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:6"&gt;
&lt;p&gt;it&amp;rsquo;s been 10 years. That&amp;rsquo;s a long-ass time.&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:7"&gt;
&lt;p&gt;if you haven&amp;rsquo;t tried a proper 4-day work week, and you have the means
to do it even with an effective 20% pay cut, then try it, it will be
life-changing.&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>./techtipsy</author><pubDate>Fri, 16 Aug 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/08/16/career/</guid></item><item><title>The dired abstraction</title><link>https://xenodium.com/the-dired-abstraction</link><description>&lt;p&gt;I recently &lt;a href="https://lmno.lol/alvaro/ready-player-mode"&gt;wrote about image-mode's next/previous item navigation&lt;/a&gt;, a feature I wanted to bring to &lt;a href="https://github.com/xenodium/ready-player"&gt;ready player mode&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I was curious to see how &lt;code&gt;image-mode&lt;/code&gt; resolved next and previous files, so I checked the associated keybinding (n) via &lt;a href="https://github.com/Wilfred/helpful"&gt;helpful-key&lt;/a&gt; (my preferred alternative to &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Key-Help.html"&gt;describe-key&lt;/a&gt;), and landed on &lt;code&gt;image-next-file&lt;/code&gt;. While this function only takes care of high-level routing, it led me to &lt;code&gt;image-mode--next-file&lt;/code&gt;, which is where the actual next/previous file resolution happens:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun image-mode--next-file (file n)
  &amp;quot;Go to the next image file in the parent buffer of FILE.
This is typically a Dired buffer, but may also be a tar/archive buffer.
Return the next image file from that buffer.
If N is negative, go to the previous file.&amp;quot;
  ...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;While &lt;code&gt;image-mode--next-file&lt;/code&gt;'s implementation details are worth &lt;a href="https://github.com/emacs-mirror/emacs/blob/e4d22abcab60ead179e7d114faa4c2def559cfbb/lisp/image-mode.el#L1264"&gt;checking out&lt;/a&gt;, its docstring already highlights the bit I found most interesting: &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Dired.html"&gt;dired&lt;/a&gt;'s involvement in the mix. I'm not sure why I initially found &lt;code&gt;dired&lt;/code&gt; usage surprising. Buffers are Emacs's backbone. They are the fundamental structures holding the content we work with, whether it’s editing text, reading logs, displaying information, and many others including file management… Dired specializes buffers for this last purpose. While &lt;code&gt;dired&lt;/code&gt; itself is a powerhouse, at its core it's just an ordered list of files.&lt;/p&gt;
&lt;p&gt;Given a location within a &lt;code&gt;dired&lt;/code&gt; buffer, we can use its helpers to find next and previous files. Like &lt;code&gt;image-mode&lt;/code&gt;, &lt;code&gt;ready-player&lt;/code&gt; now mirrors this approach (minus tar/archive handling). This got me thinking more about the &lt;code&gt;dired&lt;/code&gt; abstraction… If it quacks like a duck, and walks like a duck, then it's probably &lt;em&gt;errrm&lt;/em&gt; a &lt;code&gt;dired&lt;/code&gt; buffer. What I actually mean is that associating a &lt;code&gt;dired&lt;/code&gt; buffer to a &lt;code&gt;ready-player&lt;/code&gt; buffer effectively attaches a playlist of sorts. It doesn't quite matter how this &lt;code&gt;dired&lt;/code&gt; buffer was constructed. What's important is that it's recognized as a &lt;code&gt;dired&lt;/code&gt; buffer, so all relevant helpers remain useful.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;dired&lt;/code&gt; buffers acting as media playlists, we can easily create a directory playlist by merely pointing &lt;code&gt;dired&lt;/code&gt; to the current directory. This is the default behaviour in &lt;code&gt;ready-player&lt;/code&gt;. When you open a media file, we attach a &lt;code&gt;dired&lt;/code&gt; buffer pointing to the current directory. Play next or previous item, and you're effectively moving up and down the associated &lt;code&gt;dired&lt;/code&gt; buffer.&lt;/p&gt;
&lt;p&gt;Things get more interesting when we craft &lt;code&gt;dired&lt;/code&gt; buffers in more creative ways than just supplying a path to a directory. One of my favourite commands is &lt;a href="https://www.gnu.org/software/emacs/manual/html_node/emacs/Dired-and-Find.html"&gt;find-dired&lt;/a&gt;. It runs the &lt;a href="https://www.man7.org/linux/man-pages/man1/find.1.html"&gt;find&lt;/a&gt; utility, crafting a &lt;code&gt;dired&lt;/code&gt; buffer with its results.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/the-dired-abstraction/find.png" /&gt;&lt;/p&gt;
&lt;p&gt;For kicks, I added a &lt;code&gt;ready-player-load-dired-playback-buffer&lt;/code&gt; command to &lt;code&gt;ready-player&lt;/code&gt;, so we can just load any &lt;code&gt;dired&lt;/code&gt; buffer, including our newly generated one, courtesy of &lt;code&gt;find-dired&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;With this generated buffer loaded and &lt;code&gt;ready-player&lt;/code&gt; random playback enabled, we get to see our lucky jumps across find results.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/the-dired-abstraction/find-random.gif" /&gt;&lt;/p&gt;
&lt;p&gt;At this point I thought &amp;quot;this is prolly as far as I'll take things&amp;quot;… &lt;code&gt;ready-player&lt;/code&gt; was born to address quick access to media, typically from &lt;code&gt;dired&lt;/code&gt; itself. For deep playlist handling, there are many other Emacs media players.&lt;/p&gt;
&lt;p&gt;The thing is, with my newly found reusable &lt;code&gt;dired&lt;/code&gt; abstraction, a rough &lt;a href="https://en.wikipedia.org/wiki/M3U"&gt;m3u&lt;/a&gt; playlist experiment didn't seem that far-fetched at all. I'd need to read an &lt;code&gt;m3u&lt;/code&gt; file and generate a &lt;code&gt;dired&lt;/code&gt; buffer. I knew nothing about m3u's, other than being text files including media paths, along with optional metadata. I figured &lt;em&gt;minimal&lt;/em&gt; m3u reading support shouldn't be too difficult.&lt;/p&gt;
&lt;p&gt;If we are to create a playlist including the first three album tracks from the artist above, it'd look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#EXTM3U

#EXTINF:-1,George Benson - Dance
/absolute/path/to/Music/George Benson/Body Talk/01 Dance.mp3
#EXTINF:-1,George Benson - When Love Has Grown
/absolute/path/to/Music/George Benson/Body Talk/02 When Love Has Grown.mp3
#EXTINF:-1,George Benson - Plum
/absolute/path/to/Music/George Benson/Body Talk/03 Plum.mp3

#EXTINF:-1,George Benson - So What
/absolute/path/to/Music/George Benson/Original Album Classics/1-01 So What.mp3
#EXTINF:-1,George Benson - The Gentle Rain
/absolute/path/to/Music/George Benson/Original Album Classics/1-02 The Gentle Rain (From the Film, _The Gentle Rain_).mp3
#EXTINF:-1,George Benson - All Clear
/absolute/path/to/Music/George Benson/Original Album Classics/1-03 All Clear.mp3

#EXTINF:-1,George Benson - Footin' It
/absolute/path/to/Music/George Benson/The Shape Of Things To Come/01 Footin' It.mp3
#EXTINF:-1,George Benson - Face It Boy It's Over
/absolute/path/to/Music/George Benson/The Shape Of Things To Come/02 Face It Boy It's Over.mp3
#EXTINF:-1,George Benson - Shape Of Things To Come
/absolute/path/to/Music/George Benson/The Shape Of Things To Come/03 Shape Of Things To Come.mp3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A crude function to extract file paths into a list would look something like the following:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun ready-player--media-at-m3u-file (m3u-path)
  &amp;quot;Read m3u playlist at M3U-PATH and return files.&amp;quot;
  (with-temp-buffer
    (insert-file-contents m3u-path)
    (let ((files))
      (while (re-search-forward
              (rx bol (not (any &amp;quot;#&amp;quot; space))
                  (zero-or-more (not (any &amp;quot;\n&amp;quot;)))
                  eol) nil t)
        (when (file-exists-p (match-string 0))
          (push (match-string 0) files)))
      (nreverse files))))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Feeding our m3u file to our new function conveniently returns a list of found files:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(&amp;quot;/absolute/path/to/Music/George Benson/Body Talk/01 Dance.mp3&amp;quot;
 &amp;quot;/absolute/path/to/Music/George Benson/Body Talk/02 When Love Has Grown.mp3&amp;quot;
 &amp;quot;/absolute/path/to/Music/George Benson/Body Talk/03 Plum.mp3&amp;quot;
 &amp;quot;/absolute/path/to/Music/George Benson/Original Album Classics/1-01 So What.mp3&amp;quot;
 &amp;quot;/absolute/path/to/Music/George Benson/Original Album Classics/1-02 The Gentle Rain (From the Film, _The Gentle Rain_).mp3&amp;quot;
 &amp;quot;/absolute/path/to/Music/George Benson/Original Album Classics/1-03 All Clear.mp3&amp;quot;
 &amp;quot;/absolute/path/to/Music/George Benson/The Shape Of Things To Come/01 Footin' It.mp3&amp;quot;
 &amp;quot;/absolute/path/to/Music/George Benson/The Shape Of Things To Come/02 Face It Boy It's Over.mp3&amp;quot;
 &amp;quot;/absolute/path/to/Music/George Benson/The Shape Of Things To Come/03 Shape Of Things To Come.mp3&amp;quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Next we need to create a dired buffer from a list of files. This is where I thought things would get trickier, but I was pleasantly surprised.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;dired&lt;/code&gt; docstring had the answer:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun dired (dirname &amp;amp;optional switches)
  &amp;quot;...

If DIRNAME is a cons, its first element is taken as the directory name
and the rest as an explicit list of files to make directory entries for.
In this case, SWITCHES are applied to each of the files separately, and
therefore switches that control the order of the files in the produced
listing have no effect.

...&amp;quot;
  ...)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With that in mind, this is all it takes:&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(let ((default-directory &amp;quot;/absolute/path/to/Music/George Benson&amp;quot;))
  (dired '(&amp;quot;*My fancy m3u list*&amp;quot;
           &amp;quot;Body Talk/01 Dance.mp3&amp;quot;
           &amp;quot;Body Talk/02 When Love Has Grown.mp3&amp;quot;
           &amp;quot;Body Talk/03 Plum.mp3&amp;quot;
           &amp;quot;Original Album Classics/1-01 So What.mp3&amp;quot;
           &amp;quot;Original Album Classics/1-02 The Gentle Rain (From the Film, _The Gentle Rain_).mp3&amp;quot;
           &amp;quot;Original Album Classics/1-03 All Clear.mp3&amp;quot;
           &amp;quot;The Shape Of Things To Come/01 Footin' It.mp3&amp;quot;
           &amp;quot;The Shape Of Things To Come/02 Face It Boy It's Over.mp3&amp;quot;
           &amp;quot;The Shape Of Things To Come/03 Shape Of Things To Come.mp3&amp;quot;)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here's the &lt;code&gt;dired&lt;/code&gt; buffer to prove it:&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/the-dired-abstraction/playlist.png" /&gt;&lt;/p&gt;
&lt;p&gt;We now have all the pieces. We can wire them up in a &lt;code&gt;ready-player-load-m3u-playlist&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;From the previous snippet, you'd notice all file paths are relative to default-directory. While in the following snippet I use &lt;code&gt;try-completion&lt;/code&gt; to find the longest common substring amongst the paths, I wonder if there's a more appropriate built-in function for this? &lt;a href="https://indieweb.social/@xenodium"&gt;I'd love to hear&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class="language-{.commonlisp"&gt;(defun ready-player-load-m3u-playlist ()
  &amp;quot;Load an .m3u playlist.&amp;quot;
  (interactive)
  (let* ((m3u-path (read-file-name &amp;quot;find m3u: &amp;quot; nil nil t nil
                                   (lambda (name)
                                     (or (string-match &amp;quot;\\.m3u\\'&amp;quot; name)
                                         (file-directory-p name)))))
         (media-files (if (string-match &amp;quot;\\.m3u\\'&amp;quot; m3u-path)
                          (ready-player--media-at-m3u-file m3u-path)
                        (error &amp;quot;Not a .m3u file&amp;quot;)))
         (default-directory (file-name-directory
                             (try-completion &amp;quot;&amp;quot; media-files)))
         (m3u-fname (file-name-nondirectory m3u-path))
         (dired-buffer-name (format &amp;quot;*%s*&amp;quot; m3u-fname))
         (dired-buffer (dired (append (list dired-buffer-name)
                                      (mapcar (lambda (path)
                                                (file-relative-name path default-directory))
                                              media-files)))))
    (ready-player-load-dired-playback-buffer dired-buffer)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We're good to go now! Invoking &lt;code&gt;M-x ready-player-load-m3u-playlist&lt;/code&gt; enables us to load our &lt;code&gt;m3u&lt;/code&gt; playlist, automatically opening the first media file, and also navigate each song in the list one by one.&lt;/p&gt;
&lt;p&gt;&lt;img alt="" src="https://xenodium.github.io/images/the-dired-abstraction/benson.gif" /&gt;&lt;/p&gt;
&lt;p&gt;This was a really fun experiment. While &lt;code&gt;dired&lt;/code&gt; is often used to manage files within a directory, its magic also extends to &lt;code&gt;dired&lt;/code&gt; buffers crafted in more creative ways. &lt;code&gt;find-dired&lt;/code&gt; and &lt;code&gt;find-grep-dired&lt;/code&gt; are my two favourite built-ins. Are there other ones you like? &lt;a href="https://indieweb.social/@xenodium"&gt;Do tell&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Not long ago, I added &lt;code&gt;ready-player-load-dired-playback-buffer&lt;/code&gt; to ready-player, but &lt;code&gt;ready-player-load-m3u-playlist&lt;/code&gt; remains a local experiment (for now anyway). Let's see ;-)&lt;/p&gt;
&lt;h2&gt;Unrelated - Want your own blog?&lt;/h2&gt;
&lt;p&gt;Like this blog? Want to start a blog? Run your blog off a single file. Write from the comfort of your favourite text editor and &lt;a href="https://indieweb.social/@xenodium/112265481282475542"&gt;drag and drop to the web&lt;/a&gt;. I'm launching a blogging service at &lt;a href="https://lmno.lol"&gt;lmno.lol&lt;/a&gt;. Looking for early adopters. &lt;a href="https://indieweb.social/@xenodium"&gt;Get in touch&lt;/a&gt;.&lt;/p&gt;</description><author>xenodium.com @alvaro</author><pubDate>Fri, 16 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://xenodium.com/the-dired-abstraction</guid></item><item><title>A Normal Day in one of the Wealthiest Places on the Planet</title><link>https://jonpauluritis.com/articles/the-ultra-wealthy-at-home/</link><description>&lt;p&gt;I used to live in one of the cheapest apartments on the Newport Beach Peninsula. 450 square feet of beautiful shitty beach hut. My upstairs neighbor was a former rock star. The house behind me sold for $3.1M. Across the street was the &lt;a href="https://en.wikipedia.org/wiki/Lovell_Beach_House"&gt;Lovell Beach House&lt;/a&gt; ($6M). If we want to be generous, Kobe Bryant was one of my neighbors (~1 mile away). No clue who most of the people living there were but we were loaded to the brim with wealthy (some famous) people in the general vicinity.&lt;/p&gt;
&lt;p&gt;Newport Beach however, was not &amp;quot;exclusive&amp;quot; - there were plenty of normal people (like myself) around too. There was a great basketball game weekdays @2pm at the 38th street park. Some people bought houses when Newport Beach got messed up by a storm in the 90s (supposedly there were houses going for $300K). And every weekend during the summer we filled up with every kind of person there was hitting the beach.&lt;/p&gt;
&lt;p&gt;Despite the hodgepodge, its impossible to not notice this sort of a thing when you are walking down the street:&lt;/p&gt;
&lt;p&gt;&lt;img alt="newport house" src="https://jonpauluritis.com/img/newport-house.jpg" /&gt;&lt;/p&gt;
&lt;p&gt;And if you are anything like me, you start to wonder:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;These people have the kind of wealth that the rest of the country dreams of having... I wonder what they are doing in there?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Well it turns out that the open, modern, architecture in Newport Beach makes it easy to see stuff going on. Especially during my nightly jogs. I'm sure you'll be as surprised as I was to learn, that they mostly just sit on their asses watching Fox News (or CNN, or MSNBC).&lt;/p&gt;
&lt;p&gt;What the hell?&lt;/p&gt;
&lt;p&gt;They could be having huge raging parties with famous celebrities. Nope, parties were rare. Good parties didn't happen. They could be sitting around drinking Pappy 25, eating caviar, smoking Cohibas, riding ostriches and tigers while shooting skeet inside the house. They didn't. They could be playing strip poker with super models... yeah nope.&lt;/p&gt;
&lt;p&gt;5 years of jogging, walking, biking around the Peninsula/ Balboa Island and I saw a couple of instances of domestic abuse, (1) Porn moving being filmed, and a whole lot of nothing from the Ultra wealthy. Most of the time they were just sitting on their asses watching TV, occasionally they were having family over.&lt;/p&gt;
&lt;p&gt;In light of the &amp;quot;eat the rich&amp;quot; movement that is yet again going on in our country, I thought it might be useful to know who the ultra wealthy really are... and for the most part, they are boring old people (&amp;amp; families). They drive very nice cars. They buy expensive real estate... and they sit around watching Fox News. Pretty sad and definitely not a group to hate.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: I'm assuming the photo can be used under fair use since this is non-commercial and educational. If you are the owner of the photo and would like it removed, please contact me. the photo was found on zillow&lt;/em&gt;&lt;/p&gt;</description><author>JonPaulUritis.com</author><pubDate>Fri, 16 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jonpauluritis.com/articles/the-ultra-wealthy-at-home/</guid></item><item><title>Some thoughts about tech debt...</title><link>https://jonpauluritis.com/articles/tech-debt/</link><description>&lt;p&gt;&lt;em&gt;Note: I generally try to stay away from software engineering topics. They often don't generalize well, and can be quite pedantic. Plus there's the trap of finding yourself complaining about your day-to-day. Making an exception here...&lt;/em&gt;&lt;/p&gt;
&lt;h1&gt;Some Thoughts About Tech Debt...&lt;/h1&gt;
&lt;p&gt;When I see people complaining about tech debt, I'm always curious to understand if they are actually talking about &amp;quot;tech debt&amp;quot; as described by &lt;a href="https://youtu.be/pqeJFYwnkjE?si=XoBVHa40iNFh8Pjr"&gt;Ward Cunningham&lt;/a&gt; (&lt;em&gt;video transcription &lt;a href="https://wiki.c2.com/?WardExplainsDebtMetaphor"&gt;here&lt;/a&gt;&lt;/em&gt;) or just normal corporate disfunction (Check out my other article on this: &lt;a href="https://jonpauluritis.com/all-companies-are-fucked-up"&gt;&amp;quot;All Companies are Fucked Up&amp;quot;&lt;/a&gt;). Sort of like when you meet developers/people who think &amp;quot;agile&amp;quot; just means doing the work without any planning.&lt;/p&gt;
&lt;p&gt;To me &lt;strong&gt;&amp;quot;tech debt&amp;quot; is a useful but flawed analogy&lt;/strong&gt;, it describes a common trade off that we make in our profession - business needs now; system needs later. However using it occasionally to explain a trade off when building real world systems is completely different than describing every single thing you do in your job as some degree of tech debt, and it fails to describe the realities practitioners face when actually doing software engineering/development. One of the problems with the analogy is that its vague enough it can generally cover ALL decision making when developing software, because nothing is ever perfect.&lt;/p&gt;
&lt;p&gt;To be obtuse for a moment, lets say there are 3 types of decisions in software: crappy decisions, good-enough decisions, and great decisions. &lt;strong&gt;When you live in the &amp;quot;real world&amp;quot; and not some imaginary theoretical world, great decisions are hard to come by&lt;/strong&gt;. Just think about how difficult it is to pick a stock that will make you a deca-millionaire in 10 years... or maybe picking the perfect spouse? Truly great decisions are freaking tough and rely on a lot of luck. Theres a lot of stuff you just can't plan ahead for because thats life.&lt;/p&gt;
&lt;p&gt;Aside from the abstract concept of decision making, what else gets lumped into the &amp;quot;tech debt&amp;quot; analogy? Observationally, there's a lot of &lt;strong&gt;Shitty Engineering™&lt;/strong&gt; out there that people want to call tech debt. They could have done a decent job with whatever they were working on, but they decided not to. Ward Cunningham explicitly says this is not what he was talking about with the metaphor. Tons of these situations feature a negligible time cost to do the job well versus poorly, but &amp;quot;they&amp;quot; choose the worse option (and used the cliche &amp;quot;tech debt&amp;quot; as their shield).&lt;/p&gt;
&lt;p&gt;Why? Better to talk to your therapist about it. When people live in the real world - not everyone takes pride in their work and not everyone is capable of doing good work.&lt;/p&gt;
&lt;p&gt;Along with &lt;strong&gt;Shitty Engineering™&lt;/strong&gt;, I feel that &lt;strong&gt;Undisciplined Engineering™&lt;/strong&gt; is right up there... It seems like most companies are finally drinking the cool-aid on using an iterative approach over a large planning phase (&lt;em&gt;you know, because it's generally better&lt;/em&gt;). However there is one caveat with an iterative approach... &lt;strong&gt;you have to be disciplined and actually iterate&lt;/strong&gt;. Meaning, if you build something quickly to test a hypothesis, and the hypothesis turns out to be right (and valuable), you need to iterate and improve the systems. If you, as a software professional, left a @TODO... you need to actually go back and do the todo.&lt;/p&gt;
&lt;p&gt;As an aside, another thing I've noticed-  I hear the word refactoring throw around a lot and the word &lt;strong&gt;factoring is basically never used&lt;/strong&gt;. If you didn't architect, design, and factor your system in the first place... its not really a production system, its just a prototype and a long research phase.&lt;/p&gt;
&lt;p&gt;Back to my main point though, I feel like complaints about tech debt are typically coming from the people who built the system. Kinda weird when you think about it. It is their work but they are also the people most displeased with the state of it. What gives?&lt;/p&gt;
&lt;p&gt;Well, the for some people their concept of tech debt is roughly: &amp;quot;At this time we can't properly build a program because we need to meet a business deadline&amp;quot; (again, not the &lt;a href="https://youtu.be/pqeJFYwnkjE?si=XoBVHa40iNFh8Pjr"&gt;original metaphor&lt;/a&gt;) Guess what... it's totally reasonable in some instances to make exceptions. To beat the dead horse, that's just the real world. Sometimes a sorority girl pukes in the bathroom of the bar that you are working at, and you are the lucky person that gets to clean it up. Some times a competitor releases an important feature and you need to catch up fast. This is life.&lt;/p&gt;
&lt;p&gt;The important point in light of this concept... &lt;strong&gt;is &amp;quot;tech debt&amp;quot; something that happens occasionally? or is this the default way your team goes about building products?&lt;/strong&gt; Now, if it is the general state of affairs, &lt;em&gt;what have you personally done to fix this process failure?&lt;/em&gt; More often then not I see developers shrugging off personal responsibility for their company process here. In my book that's not cool. Highly compensated employees (&lt;em&gt;many/most software people are highly compensated employees&lt;/em&gt;) should take some ownership of the process.&lt;/p&gt;
&lt;p&gt;Continuing on with other things I see somewhat frequently (shout out to @DrThunder for reminding me)... &lt;strong&gt;Often enough people just don't understand why something was built or how it works&lt;/strong&gt;. This is especially common with required complexity. Many times people will complain about something being complex or built incorrectly, and then they come to find out that the complexity was necessary, and its not tech debt at all... just a complex feature that NEEDED to be built a certain way.&lt;/p&gt;
&lt;p&gt;There are also tons of things we build, that realistically just aren't valuable enough to warrant a refactor or even a strong design. We all know this will occasionally bite you in the ass...  sometimes someone decides a crappy feature should be a flagship feature, but its not a daily occurrence. We all intuitively know this. Not every script you write in your dev scratchpad will turn into a multi-million dollar business or feature. And not every endpoint is worth getting the latency down. So why then sit around complaining about this kind of tech debt when you know the system isn't that valuable?&lt;/p&gt;
&lt;p&gt;Another thing, which is tangential but important - &lt;strong&gt;poor interim decisions&lt;/strong&gt;. There seems to be the idea out there that &amp;quot;tech debt&amp;quot; is black and white. Either a system is in great shape or its in terrible shape. Either it needs work or doesn't need work.&lt;/p&gt;
&lt;p&gt;This belief system in particular bothers me. &lt;strong&gt;No system is perfect&lt;/strong&gt;. Most systems aren't irredeemable. So lets say that you don't get the full time necessary to clean a system up. Is all hope lost? Hell no. &lt;strong&gt;What's wrong with trying to get as close as possible to the right solution?&lt;/strong&gt; What's wrong with setting stuff up so the next time you're in there you can do more of gods work getting the system to where it needs to be? If the tech debt is that bad, progressive improvements should be no problem. Solid improvements should happen every time you work on the system. Any valuable system should give you numerous opportunities for improvement. So how come there aren't more &lt;strong&gt;incremental improvements?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The last thing I will say before I close out. Unless I have just inherited a legacy system its rare to catch me complaining about tech debt in a system. Why? Well for starters, I'm generally a happy person. That helps. The next important thing though is if I own a system and its in bad shape I'll work some extra hours to get it in to respectable shape. Why? Because I don't like being miserable at work everyday. I also don't like giving away my time, but thats the real world. More often than not you can do some major fixes pretty quick if you don't let the codebase shift too much while you are working on it. If you want a system to be in good shape you have to take ownership of the system.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;/rant&amp;gt;&lt;/code&gt;&lt;/p&gt;</description><author>JonPaulUritis.com</author><pubDate>Fri, 16 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jonpauluritis.com/articles/tech-debt/</guid></item><item><title>My Photography is Featured at Philz Coffee in Potrero Hill</title><link>https://thomashunter.name/posts/2024-08-16-photography-featured-at-philz-potrero</link><author>Thomas Hunter II</author><pubDate>Fri, 16 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://thomashunter.name/posts/2024-08-16-photography-featured-at-philz-potrero</guid></item><item><title>2024-08-16</title><link>https://ho.dges.online/pictures/2024-08-16/</link><description/><author>ho.dges.online</author><pubDate>Fri, 16 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-08-16/</guid></item><item><title>Interesting Browser Engine Bugs, Part 3: &amp;lt;select&amp;gt; Tags Crash Chrome</title><link>/development/2024/08/16/browser-engine-bugs-pt3.html</link><description>&lt;h3 id="other-posts-in-this-series"&gt;Other Posts in This Series&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href="/development/2024/06/13/browser-engine-bugs-pt1.html"&gt;Part 1: Test Timeouts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="/development/2024/08/10/browser-engine-bugs-pt2.html"&gt;Part 2: On Video Conferencing&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Part 3: &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;select&amp;gt;&lt;/code&gt; Tags Crash Chrome (You’re here!)&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;I hope you enjoyed (and possibly sympathized) with the bugs in this series. Somehow, I’m the one that manages to find and diagnose a lot of these and they were interesting to debug.&lt;/p&gt;

&lt;p&gt;This last bug really wasn’t totally out of our control, but a change to the browser implementation of &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;select&amp;gt;&lt;/code&gt; tags uncovered a severe performance problem in our application. This is also the first bug that caused real problems for end users, and required us to put in a code change to mitigate some adverse affects resulting from the issue.&lt;/p&gt;

&lt;h2 id="chromium-tab-hangscrashes-on-select-with-large-number-of-options"&gt;chromium: Tab hangs/crashes on select with large number of options&lt;/h2&gt;

&lt;p&gt;Everybody is familiar with &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;select&amp;gt;&lt;/code&gt; tags. They’re ubiquitous in HTML templates.&lt;/p&gt;

&lt;p&gt;Have you ever though about how browsers render them? Or how about if you try to force an insane number of options into a single &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;select&amp;gt;&lt;/code&gt;? What if you gave a browser something like…&lt;/p&gt;

&lt;div class="language-html highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;select&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"1"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Super important&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"2"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Definitely need this&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"3"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Can't live without&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- roughly 10 thousand &amp;lt;option&amp;gt; tags later --&amp;gt;&lt;/span&gt;

  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"9999"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Must have&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;option&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"10000"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;THIS SUSTAINS MY EXISTENCE&lt;span class="nt"&gt;&amp;lt;/option&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/select&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;What would happen? It’s not really a rhetorical question. In our case, Chrome happily renders this – and fairly quickly! At least for a while.&lt;/p&gt;

&lt;p&gt;On May 16, 2024, a bug was posted to the chromium issue tracker: &lt;a href="https://issues.chromium.org/issues/341095522"&gt;Tab hangs/crashes on select with large numbers of options&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’ve followed the series, you can probably guess what’s next. This bug was introduced into a version of chromium (125.x) and packaged into Chrome, which was then pushed out by our managed IT system at a later date. Thus, our first introduction to the issue did not directly correlate with the chromium bug tracker.&lt;/p&gt;

&lt;p&gt;We received reports starting May 21 that pages were “slow” to load. We looked at our internal application performance monitoring dashboards and saw no degradation, a surprising revelation that in hindsight pointed to a browser issue. Network traffic was fine (which would have been caught by APM), but at the time we did not collect page load times, browser paint duration, etc. to be able to detect it (we have since remedied this).&lt;/p&gt;

&lt;p&gt;It took a while to narrow the common denominator to Chrome 125.x. When we did, the problem was &lt;em&gt;very&lt;/em&gt; obvious. “Slow” page loads, in my professional opinion, were vastly understating the problem – I managed to crash Brave (also built on chromium) on a few pages.&lt;/p&gt;

&lt;p&gt;Further investigation into the issue revealed problems on only a handful of pages. These did contain suspect &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;select&amp;gt;&lt;/code&gt; tags, so we spent a while figuring out what was populating them.&lt;/p&gt;

&lt;p&gt;It turns out that the affected pages contained &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;select&amp;gt;&lt;/code&gt; tags populated with tens of thousands of options. These were used to filter through a list of available users.&lt;/p&gt;

&lt;p&gt;But… that list of users was never filtered down. It was literally a list of 10,000+ past and present employees, but never accounted for employees that were no longer with the company, didn’t have access to the tool, or were otherwise irrelevant to the selection.&lt;/p&gt;

&lt;p&gt;Oops.&lt;/p&gt;

&lt;p&gt;We added the appropriate filters to mitigate the problem, IT pushed out the next stable version of Chrome a few days later, and the problems (both the browser bug and missing user filters!) were resolved.&lt;/p&gt;

&lt;p&gt;This bug was interesting because as web developers, we like to abstract away the platform (browser) that the front end runs on and assume that it’s a constant. However, as was the case with this issue, the browser isn’t static, and changes to it can and do have performance effects on your application, even if you haven’t changed the code.&lt;/p&gt;

&lt;p&gt;That’s all for now. Thanks for reading!&lt;/p&gt;</description><author>ty-porter</author><pubDate>Fri, 16 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">/development/2024/08/16/browser-engine-bugs-pt3.html</guid></item><item><title>Configuring GitHub Discussions to provide Blog Comments</title><link>https://jasoneckert.github.io/myblog/github-discussions-blog/</link><description>&lt;p&gt;&lt;img alt="Comments button" src="comments.png#center" title="Comments button" /&gt;&lt;/p&gt;
&lt;p&gt;There are a few reasons why I&amp;rsquo;ve avoided adding a comment section to my blog these past two decades. Firstly, it can promote inappropriate and low effort responses&amp;hellip; especially if comments can be made anonymously. As a result, my home page encourages readers to email me directly. I&amp;rsquo;ve received well over a thousand emails from readers over the years, and I&amp;rsquo;ve enjoyed replying to each one. Secondly, introducing a comment section requires the execution of dynamic content that usually stores comments within a database. I instead prefer to use a simple, static website for my personal blog that can be easily hosted anywhere I choose. This also means I don&amp;rsquo;t have to configure and maintain databases.&lt;/p&gt;</description><author>Jason Eckert's Website and Blog</author><pubDate>Fri, 16 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jasoneckert.github.io/myblog/github-discussions-blog/</guid></item><item><title>Let's reanimate</title><link>https://whackylabs.com/js/reactnative/animation/2024/08/15/let-reanimate/</link><description>&lt;p&gt;Drawing text and images is one thing but the real test of a UI framework is in how good is it with animating content.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://imgflip.com/i/9098i4"&gt;&lt;img src="https://i.imgflip.com/9098i4.jpg" title="made at imgflip.com" /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And my test for animation is the classic &lt;a href="https://developer.apple.com/library/archive/samplecode/MoveMe/Introduction/Intro.html#//apple_ref/doc/uid/DTS40007315"&gt;MoveMe&lt;/a&gt; based on the Apple’s sample code.&lt;/p&gt;

&lt;p&gt;The idea is to draw three boxes on the screen. When selected the box changes color and scales up and then can be moved around with the drag gesture and eventually restores back to original color and size when released.&lt;/p&gt;

&lt;p&gt;Let’s build that sample using React Native’s Reanimated library.&lt;/p&gt;

&lt;h3 id="setup"&gt;Setup&lt;/h3&gt;
&lt;p&gt;I’m following the official docs but not using their template. So I’ve a basic project created with the blank template and installed the dependencies&lt;/p&gt;

&lt;div class="language-plaintext highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;npx create-expo-app moveme --template blank
npx expo install react-native-reanimated
npx expo install react-native-gesture-handler
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, I added the plugin by editing &lt;code class="language-plaintext highlighter-rouge"&gt;babel.config.js&lt;/code&gt; to&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cache&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;babel-preset-expo&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-reanimated/plugin&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then draw 3 squares on screen:&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StatusBar&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;expo-status-bar&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;View&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Animated&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;react-native-reanimated&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;square&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;StatusBar&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"auto"&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Square&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Square&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Square&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;StyleSheet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;flex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#fff&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;alignItems&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;space-evenly&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;square&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img alt="set up" src="https://whackylabs.com/assets/reanimate/setup.png" /&gt;&lt;/p&gt;

&lt;h3 id="add-gesture-handler"&gt;Add gesture handler&lt;/h3&gt;
&lt;p&gt;To add support for gesture handlers we first need to wrap the content within the &lt;code class="language-plaintext highlighter-rouge"&gt;GestureHandlerRootView&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GestureHandlerRootView&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Square&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Square&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Square&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;GestureHandlerRootView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And then wrap each &lt;code class="language-plaintext highlighter-rouge"&gt;Square&lt;/code&gt; within &lt;code class="language-plaintext highlighter-rouge"&gt;GestureDetector&lt;/code&gt;&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gesture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Gesture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pan&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GestureDetector&lt;/span&gt; &lt;span class="na"&gt;gesture&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;gesture&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;square&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;GestureDetector&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id="handle-gesture-events"&gt;Handle gesture events&lt;/h3&gt;
&lt;p&gt;To handle gesture we first need to create a &lt;code class="language-plaintext highlighter-rouge"&gt;SharedValue&lt;/code&gt; which is like &lt;code class="language-plaintext highlighter-rouge"&gt;State&lt;/code&gt; but for animation states. For example, to change the background color when selected we need to listen to &lt;code class="language-plaintext highlighter-rouge"&gt;onBegin&lt;/code&gt; and &lt;code class="language-plaintext highlighter-rouge"&gt;onFinalize&lt;/code&gt; events and update the style:&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSharedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useAnimatedStyle&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isPressed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gesture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Gesture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pan&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onBegin&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;isPressed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onFinalize&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;isPressed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GestureDetector&lt;/span&gt; &lt;span class="na"&gt;gesture&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;gesture&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;square&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;animStyle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;GestureDetector&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Supporting drag is similar. We need to store start and current positions and then update the current position on &lt;code class="language-plaintext highlighter-rouge"&gt;onChange&lt;/code&gt; event. The &lt;code class="language-plaintext highlighter-rouge"&gt;onChange&lt;/code&gt; provides the delta change that we then need to add to the start position to calculate the final current position. And then, finally at the &lt;code class="language-plaintext highlighter-rouge"&gt;onFinalize&lt;/code&gt; event we can sync the start and current positions.&lt;/p&gt;

&lt;div class="language-jsx highlighter-rouge"&gt;&lt;div class="highlight"&gt;&lt;pre class="highlight"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;Square&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isPressed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSharedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;startPos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSharedValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useSharedValue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;animStyle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;useAnimatedStyle&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;backgroundColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isPressed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;red&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;blue&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;transform&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;translateX&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;translateY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;withSpring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isPressed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mf"&gt;1.2&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;gesture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Gesture&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Pan&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onBegin&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;isPressed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onChange&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;startPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translationX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;startPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;translationY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;onFinalize&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;isPressed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;startPos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;y&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;GestureDetector&lt;/span&gt; &lt;span class="na"&gt;gesture&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;gesture&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Animated&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;square&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;animStyle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;GestureDetector&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And there you have it&lt;/p&gt;

&lt;p&gt;&lt;img alt="final" src="https://whackylabs.com/assets/reanimate/final.gif" /&gt;&lt;/p&gt;

&lt;p&gt;The source code is available on &lt;a href="https://github.com/chunkyguy/MoveMe/tree/main/reactnative"&gt;https://github.com/chunkyguy/MoveMe/tree/main/reactnative&lt;/a&gt;&lt;/p&gt;

&lt;h3 id="references"&gt;References&lt;/h3&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;a href="https://docs.swmansion.com/react-native-reanimated"&gt;react-native-reanimated&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://docs.swmansion.com/react-native-gesture-handler"&gt;react-native-gesture-handler&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://youtu.be/4HUreYYoE6U?si=rZ53Yvft9nbdlGAC"&gt;The basics of PanGestureHandler with React Native Reanimated 2&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href="https://whackylabs.com/layout/ui/swift/ios/2022/12/12/layout-driven-ui/"&gt;Data-Driven UI with UIKit&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><author>Whacky Labs</author><pubDate>Thu, 15 Aug 2024 18:16:00 GMT</pubDate><guid isPermaLink="true">https://whackylabs.com/js/reactnative/animation/2024/08/15/let-reanimate/</guid></item><item><title>My Developer Tool List</title><link>https://dheinemann.com/my-developer-tool-list/</link><description>Software are like tools; the right one for the right job makes everything a lot easier. Here's the software that I use day-to-day as a developer on Windows.</description><author>Dave Heinemann</author><pubDate>Thu, 15 Aug 2024 01:10:11 GMT</pubDate><guid isPermaLink="true">https://dheinemann.com/my-developer-tool-list/</guid></item><item><title>Book Review: The Mom Test</title><link>https://abdulapopoola.com/2024/08/14/book-review-the-mom-test/</link><description>The Mom Test is a concise and impactful guide for startup founders, emphasizing the importance of asking the right questions to distinguish genuine customer needs. It highlights common mistakes and provides practical advice.</description><author>CodeKraft</author><pubDate>Wed, 14 Aug 2024 18:00:00 GMT</pubDate><guid isPermaLink="true">https://abdulapopoola.com/2024/08/14/book-review-the-mom-test/</guid></item><item><title>Visual Data Structures Cheat-Sheet</title><link>https://photonlines.substack.com/p/visual-data-structures-cheat-sheet</link><description>A visual overview of some of the key data-structures used in the real world.</description><author>Photon-Lines Substack</author><pubDate>Wed, 14 Aug 2024 15:19:55 GMT</pubDate><guid isPermaLink="true">https://photonlines.substack.com/p/visual-data-structures-cheat-sheet</guid></item><item><title>I'm leaving Twitter/X</title><link>https://nicolaiarocci.com/im-leaving-twitter/x/</link><description>&lt;p&gt;I&amp;rsquo;m abandoning Twitter/X. I&amp;rsquo;ll freeze the account without deleting it; never say never, but I don&amp;rsquo;t plan on coming back. I no longer feel comfortable on that platform and haven&amp;rsquo;t been for a while.&lt;/p&gt;
&lt;p&gt;If you still want to follow me (I&amp;rsquo;d love for you to do so), the best option is &lt;a href="https://nicolaiarocci.com"&gt;my website&lt;/a&gt;  where I always post first (RSS feed &lt;a href="https://nicolaiarocci.com/index.xml"&gt;here&lt;/a&gt;), the &lt;a href="https://buttondown.email/nicolaiarocci"&gt;mailing list&lt;/a&gt;, or &lt;a href="https://fosstodon.org/@nicola"&gt;Mastodon&lt;/a&gt;.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Wed, 14 Aug 2024 09:20:39 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/im-leaving-twitter/x/</guid></item><item><title>Note 146</title><link>https://qubyte.codes/notes/1723599483863</link><description>&lt;p&gt;My view from the Rikugien tea house.&lt;/p&gt;

    &lt;img alt="In the foreground (close to me) is the trunk of a gnarled, ornamental pine tree. Below it is grass. Its branches and needles stretch out over a calm lake. The green opposing shore can be seen through the branches. There other side is grassy with woodland behind." src="https://qubyte.codes/images/1723599325433.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Wed, 14 Aug 2024 04:38:03 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1723599483863</guid></item><item><title>Note 145</title><link>https://qubyte.codes/notes/1723599281650</link><description>&lt;p&gt;Iced matcha and a fireworks themed Japanese confection at a tea house in Rikugien (garden).&lt;/p&gt;

    &lt;img alt="A large, bowl shaped cup of iced green matcha next to a confection, on a plastic (lacquer effect) tray. The confection is on a rectangle of paper, beside a simple wooden knife to eat it with. The tray is on a wooden bench." src="https://qubyte.codes/images/1723599086492.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Wed, 14 Aug 2024 04:34:41 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1723599281650</guid></item><item><title>🛠️ Managing Development Environments with Mise</title><link>https://james-carr.org/posts/2024-08-14-managing-development-environments-with-mise/</link><description>As I&amp;rsquo;ve started down the Indie Hacker road, one thing that has come up as I work on various projects is I need something to manage multiple versions of different binaries. I had used nvm and pyenv in the past (and rvm before that) so I started down the path of looking for the same type of tool in the elixir world. Thankfully, this led me down the path of more generic solutions to versioning these tools.</description><author>James Carr</author><pubDate>Wed, 14 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://james-carr.org/posts/2024-08-14-managing-development-environments-with-mise/</guid></item><item><title>What the heck is a pod sandbox?</title><link>https://sklar.rocks/what-is-a-pod-sandbox/</link><description>&lt;h2 id="it-always-begins-with-a-pagerduty-alert"&gt;It always begins with a PagerDuty alert...&lt;/h2&gt;
&lt;p&gt;A few weeks ago, I got a PagerDuty ping with this error:&lt;/p&gt;
&lt;pre class="giallo" style="color: #E1E4E8; background-color: #24292E;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;[FIRING:1] :fire: KubeContainerWaitingQuestDB&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;Summary: Container in waiting state for longer than 1 hour&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Huh, well that could be caused by a bunch of things. Let's use &lt;code&gt;kubectl&lt;/code&gt; to check the Pod's status:&lt;/p&gt;
&lt;pre class="giallo" style="color: #E1E4E8; background-color: #24292E;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span&gt;Failed to create pod sandbox: rpc error: code = Unknown desc = failed&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;to get sandbox image &amp;quot;602401143452.dkr.ecr.eu-west-1.amazonaws.com/eks/pause:3.5&amp;quot;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;failed to pull image &amp;quot;602401143452.dkr.ecr.eu-west-1.amazonaws.com/eks/pause:3.5&amp;quot;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;failed to pull and unpack image &amp;quot;602401143452.dkr.ecr.eu-west-1.amazonaws.com/eks/pause:3.5&amp;quot;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;failed to resolve reference &amp;quot;602401143452.dkr.ecr.eu-west-1.amazonaws.com/eks/pause:3.5&amp;quot;:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;pull access denied, repository does not exist or may require authorization:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;authorization failed:&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span&gt;no basic authcredentials&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Uhhh... &lt;code&gt;pause:3.5&lt;/code&gt;? What is this thing? Why doesn't the node have access to it?&lt;/p&gt;
&lt;p&gt;After a bit of digging (otherwise known as copy/pasting the error logs into Google), it turns out that I hit a GitHub issue (&lt;a href="https://github.com/awslabs/amazon-eks-ami/issues/1597" rel="external"&gt;Sandbox container image being GC'd in 1.29&lt;/a&gt;) in &lt;a href="https://github.com/awslabs/amazon-eks-ami" rel="external"&gt;awslabs/amazon-eks-ami&lt;/a&gt;. Because my node was running out of disk space, this &lt;code&gt;pause:3.5&lt;/code&gt; container got garbage collected by the container runtime, containerd. Subsequently, when a new Pod was created and containerd attempted to re-pull this image, it wasn't properly authenticated with ECR (using credentials from &lt;code&gt;aws eks get-docker-login&lt;/code&gt;), so the kubelet emitted the above error event associated with the Pod.&lt;/p&gt;
&lt;p&gt;While that seemed to explain the issue at hand, it didn't answer the question of &lt;em&gt;why&lt;/em&gt; this &lt;code&gt;pause:3.5&lt;/code&gt; container was needed in the first place! Is this a sandbox container? And if so, what does that mean? Since the container is required by every Pod on my Node, clearly this is an important construct in the Kubernetes ecosystem.&lt;/p&gt;
&lt;h2 id="what-is-a-pod-sandbox"&gt;What is a Pod Sandbox?&lt;/h2&gt;
&lt;p&gt;I knew that different containers running inside the same Pod share resources like filesystems and networks. This lets me, for example, create a volume mount in one container and access it from another one in the Pod.&lt;/p&gt;
&lt;p&gt;The sandbox creates an environment which allows a Pod's containers to safely share these resources, while still isolating them from the rest of the node. This is detailed more in a k8s &lt;a href="https://kubernetes.io/blog/2016/12/container-runtime-interface-cri-in-kubernetes/#:~:text=Before%20starting%20a%20pod%2C%20kubelet%20calls%20RuntimeService.RunPodSandbox%20to%20create%20the%20environment" rel="external"&gt;blog post&lt;/a&gt; introducing the Container Runtime Interface (CRI) back in 2016:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A Pod is composed of a group of application containers in an isolated environment with resource constraints. In CRI, this environment is called PodSandbox. We intentionally leave some room for the container runtimes to interpret the PodSandbox differently based o in the future.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Ok, so the sandbox is a group of namespaces? Then what does this &lt;code&gt;pause&lt;/code&gt; container do? Like most things, let's &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/build/pause/linux/pause.c" rel="external"&gt;go to the source&lt;/a&gt; to see if we can get another clue. After some basic argument handling (for &lt;code&gt;-v&lt;/code&gt;) and lifecycle management around &lt;code&gt;SIGINT&lt;/code&gt;, &lt;code&gt;SIGTERM&lt;/code&gt;, and &lt;code&gt;SIGCHLD&lt;/code&gt;, we get to the main execution loop:&lt;/p&gt;
&lt;pre class="giallo" style="color: #E1E4E8; background-color: #24292E;"&gt;&lt;code&gt;&lt;span class="giallo-l"&gt;&lt;span style="color: #F97583;"&gt;  for&lt;/span&gt;&lt;span&gt; (;;)&lt;/span&gt;&lt;/span&gt;
&lt;span class="giallo-l"&gt;&lt;span style="color: #B392F0;"&gt;    pause&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Looking at the &lt;a href="https://man7.org/linux/man-pages/man2/pause.2.html" rel="external"&gt;pause(2) man page&lt;/a&gt;, it turns out that all this does is put the thread to sleep. So why does every Pod need an extra container that just sleeps?&lt;/p&gt;
&lt;p&gt;Since the docs mentioned that a PodSandbox is implementation specfic, I starting digging around in containerd. There, I found the containerd cri plugin architecture &lt;a href="https://github.com/containerd/containerd/blob/main/docs/cri/architecture.md" rel="external"&gt;on GitHub&lt;/a&gt;. From the architecture doc, one of the Pod initialization steps is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;cri uses containerd internal to create and start a special &lt;a href="https://www.ianlewis.org/en/almighty-pause-container" rel="external"&gt;pause container&lt;/a&gt; (the sandbox container) and put that container inside the pod’s cgroups and namespace (steps omitted for brevity);&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="finally-the-answer"&gt;Finally, the answer!&lt;/h2&gt;
&lt;p&gt;Once I clicked the link from the above quote, which led me to &lt;a href="https://www.ianlewis.org/" rel="external"&gt;Ian Lewis's blog&lt;/a&gt;, I finally had my answer.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;In Kubernetes, the pause container serves as the “parent container” for all of the containers in your pod. The pause container has two core responsibilities. First, it serves as the basis of Linux namespace sharing in the pod. And second, with PID (process ID) namespace sharing enabled, it serves as PID 1 for each pod and reaps zombie processes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If only I had found this article at the beginning of my journey! Ian goes into great detail about how the pause container not only sleeps, but also manages the lifecycle of child processes by reaping zombies, which is something that I missed when I first reviewed the container executable's source code. There are also other articles on the blog that discuss how container runtimes work at a low level, which is really interesting stuff. I highly recommend reading these!&lt;/p&gt;
&lt;p&gt;While this post could be boiled down to simply sharing a link to the aforementioned blog post, I thought that it would be useful to publicly document my research process and train of thought. Many times, it takes a combination of Google searches, blog posts, READMEs, docs, and source files to solve or understand a particular issue. I find that by going the extra mile and taking the time to properly investigate a problem, you can really improve your understanding of the systems that you work with every day, making it that much easier to solve the next issue that pops up.&lt;/p&gt;
&lt;p&gt;Back to the original PagerDuty alert. I originally solved the problem by cordoning, draining, and deleting the node. The Pod was re-scheduled to a new node (with a downloaded &lt;code&gt;pause&lt;/code&gt; container image), and started up with no issues.&lt;/p&gt;
&lt;p&gt;While I simply could've left it there, knowing that I had a remediation plan in case I ever experienced that error again, I'm glad that I went the extra mile to fully investigate the problem. As a result, I not only feel more confident in my choice to remove the node, but I also have a deeper understanding of how Kubernetes works at a fundamental level and add that bit knowledge to my toolkit.&lt;/p&gt;</description><author>Steven Sklar | My Blog</author><pubDate>Wed, 14 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://sklar.rocks/what-is-a-pod-sandbox/</guid></item><item><title>Challengers</title><link>https://mehulkar.com/blog/2024/08/challengers?utm_source=rss</link><description>&lt;div class="letterboxd-movie-data-content"&gt;
   &lt;p&gt;&lt;img src="https://a.ltrbxd.com/resized/film-poster/8/4/2/3/0/1/842301-challengers-0-600-0-900-crop.jpg?v=a7cd63cbef" /&gt;&lt;/p&gt; &lt;p&gt;Good movie about gay techno&lt;/p&gt; 
  &lt;p&gt;Rated 2.5 stars.&lt;/p&gt;&lt;p&gt;
  &lt;/p&gt;&lt;div class="float-clear"&gt;&lt;/div&gt;
&lt;/div&gt;

        &lt;p&gt;Thanks for reading this post via RSS!&lt;/p&gt;</description><author>Mehul Kar's blog</author><pubDate>Wed, 14 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://mehulkar.com/blog/2024/08/challengers?utm_source=rss</guid></item><item><title>A Better OS Design 2: Microkernel Performance</title><link>https://gavinhoward.com/2024/08/a-better-os-design-2-microkernel-performance/</link><description>Here are some more ideas for a better OS.</description><author>Gavin D. Howard</author><pubDate>Wed, 14 Aug 2024 00:17:02 GMT</pubDate><guid isPermaLink="true">https://gavinhoward.com/2024/08/a-better-os-design-2-microkernel-performance/</guid></item><item><title>Refining the Flow: A Streamlined Markdown/Git-Based Task Management System for Solo Developers</title><link>https://pankajpipada.com/posts/2024-08-13-taskmgmt-2/</link><description>This post explores a markdown-based task management system designed for solo developers, balancing simplicity with effective project management.</description><author>Scramblings</author><pubDate>Tue, 13 Aug 2024 15:30:00 GMT</pubDate><guid isPermaLink="true">https://pankajpipada.com/posts/2024-08-13-taskmgmt-2/</guid></item><item><title>mwclient 0.11</title><link>https://river.me/blog/mwclient-0.11/</link><description>mwclient 0.11 has released!</description><author>River Writes - A MediaWiki Blog</author><pubDate>Tue, 13 Aug 2024 12:20:10 GMT</pubDate><guid isPermaLink="true">https://river.me/blog/mwclient-0.11/</guid></item><item><title>What makes you not give up on life?</title><link>https://jonpauluritis.com/articles/what-makes-you-not-give-up-on-life/</link><description>&lt;p&gt;The question was asked &amp;quot;What makes you not give up on life&amp;quot;. This is something I feel strongly about so here is my answer:&lt;/p&gt;
&lt;p&gt;Humans are a bunch of talking communal apes hurling through the galaxy on an organic space ship that as far as we know there is nothing else like it in the known universe.&lt;/p&gt;
&lt;p&gt;Any time before 100 years ago by the age of 25 you and/or half your friends would have died or been maimed for the rest of your life by a war, a disease, a natural disaster, or some other crazy shit that you have no control over... But since you are reading this, you likely aren't going to be eaten by a sabertoothed tiger, incapacitated by a brain eating microbe, or killed by some crazy dictator who woke up on the wrong side of the bed. That in itself is amazing.&lt;/p&gt;
&lt;p&gt;The fact that I am able to communicate all of this to you because some dude got the crazy idea to make sand think, and then another dude thought that his thinking sand should be able to talk to your thinking sand is beyond AMAZING.&lt;/p&gt;
&lt;p&gt;You live in a time during  where you are endowed with the freedom (in 165 countries at least) that you can do basically whatever you want when you want, with only the slight inconvenience of having to pay for it, which again due to the time in history, is more of an inconvenience than a deterrent.&lt;/p&gt;
&lt;p&gt;If you want to paint a painting, go paint. If you want to talk to the pretty girl/guy at the coffee shop go talk to them! If you want to cliff jump in Norway you can! &lt;strong&gt;If you want to go to burning man, do a bunch of drugs, get naked and ride around on a bike and get a sunburn.&lt;/strong&gt;.. you can do that too! There is an infinite number of amazing, beautiful, crazy things to tickle your fancy in this world. You just need to walk out the door and go do it!!!&lt;/p&gt;
&lt;p&gt;So what makes me not want to give up on life? I have experienced the tiniest little bit of it, and its AWESOME. The people, the places, the thoughts, the art... &lt;strong&gt;literally the only fucking problem (in the western world) are the people, companies, schools, governments, etc that are trying to make us unhappy so we buy more shit. That's not what it is all about... so ignore them. Get off their rails. Their version of what this is about sucks.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I cannot recommend enough that you should get out there and try it.&lt;/p&gt;</description><author>JonPaulUritis.com</author><pubDate>Tue, 13 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://jonpauluritis.com/articles/what-makes-you-not-give-up-on-life/</guid></item><item><title>Default explicit equality operator</title><link>https://studiofreya.org/cpp/2024-08-13-default-explicit-equality-operator/</link><description>&lt;p&gt;C++20 introduced a defaulted explicit equality operator (&lt;code&gt;==&lt;/code&gt;). It instructs the compiler to implement a piecewise equality check between two instances of the same &lt;code&gt;class&lt;/code&gt; or &lt;code&gt;struct&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;As a regular member function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-cpp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #75715e;"&gt;#include&lt;/span&gt; &lt;span style="color: #75715e;"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;&lt;span style="color: #75715e;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;struct&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;X&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;int&lt;/span&gt; x &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    std&lt;span style="color: #f92672;"&gt;::&lt;/span&gt;string name;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;bool&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;operator&lt;/span&gt;&lt;span style="color: #f92672;"&gt;==&lt;/span&gt;(&lt;span style="color: #66d9ef;"&gt;const&lt;/span&gt; X &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;) &lt;span style="color: #66d9ef;"&gt;const&lt;/span&gt; &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;default&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;auto&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;main&lt;/span&gt;() &lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    X a{}, b{};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;bool&lt;/span&gt; equal &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; (a &lt;span style="color: #f92672;"&gt;==&lt;/span&gt; b);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; equal &lt;span style="color: #f92672;"&gt;?&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt; &lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;As a friend member function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-cpp"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;struct&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;F&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;int&lt;/span&gt; x &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    std&lt;span style="color: #f92672;"&gt;::&lt;/span&gt;string name;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;friend&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;bool&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;operator&lt;/span&gt;&lt;span style="color: #f92672;"&gt;==&lt;/span&gt;(&lt;span style="color: #66d9ef;"&gt;const&lt;/span&gt; F &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;, &lt;span style="color: #66d9ef;"&gt;const&lt;/span&gt; F &lt;span style="color: #f92672;"&gt;&amp;amp;&lt;/span&gt;) &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;default&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #66d9ef;"&gt;auto&lt;/span&gt; &lt;span style="color: #a6e22e;"&gt;main&lt;/span&gt;() &lt;span style="color: #f92672;"&gt;-&amp;gt;&lt;/span&gt; &lt;span style="color: #66d9ef;"&gt;int&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    F g{}, h{};
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;bool&lt;/span&gt; friend_equal &lt;span style="color: #f92672;"&gt;=&lt;/span&gt; (g &lt;span style="color: #f92672;"&gt;==&lt;/span&gt; h);
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;    &lt;span style="color: #66d9ef;"&gt;return&lt;/span&gt; friend_equal &lt;span style="color: #f92672;"&gt;?&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;0&lt;/span&gt; &lt;span style="color: #f92672;"&gt;:&lt;/span&gt; &lt;span style="color: #ae81ff;"&gt;1&lt;/span&gt;;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This default comparison replaces the manually implemented comparison and equality operator.&lt;/p&gt;
&lt;p&gt;More information at &lt;a href="https://en.cppreference.com/w/cpp/language/default_comparisons"&gt;https://en.cppreference.com/w/cpp/language/default_comparisons&lt;/a&gt;&lt;/p&gt;</description><author>Studiofreya SSG Site</author><pubDate>Tue, 13 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://studiofreya.org/cpp/2024-08-13-default-explicit-equality-operator/</guid></item><item><title>FWD: RE: radioactive fungus email from grandma</title><link>https://taylor.town/radioactive-fungi</link><description>Whoa! This is very interesting! My question: is the fungi now radioactive?</description><author>taylor.town</author><pubDate>Tue, 13 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://taylor.town/radioactive-fungi</guid></item><item><title>Structured Output, Functions and Prompting</title><link>https://www.danielcorin.com/posts/2024/structure-and-functions-and-prompting/</link><description>Structured Output, Functions and Prompting</description><author>Thought Eddies</author><pubDate>Tue, 13 Aug 2024 00:15:27 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2024/structure-and-functions-and-prompting/</guid></item><item><title>The Phoebe Strategy</title><link>https://dustinfreeman.org/blog/the-phoebe-strategy/</link><description>Based on recent events, I propose that we should all be aware of a new meta in psy-op strategies&amp;#8230; Obviously, no sensible person believes that JD Vance had sex with a couch. The humour of this claim, fair or not, is about whether one can argue that “he seems like the kind of guy that [&amp;#8230;]</description><author>Dustin Freeman</author><pubDate>Mon, 12 Aug 2024 23:47:06 GMT</pubDate><guid isPermaLink="true">https://dustinfreeman.org/blog/the-phoebe-strategy/</guid></item><item><title>Google DeepMind's Grandmaster-Level Chess Without Search</title><link>https://www.hlfshell.ai/posts/deepmind-grandmaster-chess-without-search/</link><description>tl;dr Google DeepMind released a paper claiming that, without search, a transformer architecture can be utilized to achieve grandmaster level play in chess. But what does that mean? I&amp;amp;rsquo;m giving a paper club talk on the subject, so I decided to write up my own analysis of the paper.
[Paper] | [Code] | [My Slides]
 Chess Without* Search This paper got a fair amount of press, but it seems that much of the discussion surrounding it is confused.</description><author>hlfshell</author><pubDate>Mon, 12 Aug 2024 19:30:00 GMT</pubDate><guid isPermaLink="true">https://www.hlfshell.ai/posts/deepmind-grandmaster-chess-without-search/</guid></item><item><title>Note 144</title><link>https://qubyte.codes/notes/1723458371793</link><description>&lt;p&gt;Yeah, fuck ‘im up Shinji. Now get back in the fucking robot.&lt;/p&gt;

    &lt;img alt="A photo of a diorama of the Evangelion scene in the school yard where Shinji gets hit in the face, and then goaded into hitting back." src="https://qubyte.codes/images/1723458271328.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Mon, 12 Aug 2024 13:26:11 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1723458371793</guid></item><item><title>I Have Two Friends an Introverts Guide to Not Chasing Friendships</title><link>https://saeedesmaili.com/notes/i-have-two-friends-an-introverts-guide-to-not-chasing-friendships/</link><description>&lt;p&gt;This excellent essay from Karolina was the best I&amp;rsquo;ve read this year. I can related with many of her point on why friendship muscle looks different for every person.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Abroad, I meet local people who don&amp;rsquo;t hang out with expats because they have their Martas. Every time I hear a Dutch or British person say &amp;ldquo;we&amp;rsquo;ve known each other since we were little&amp;rdquo;, I can&amp;rsquo;t help but feel a pang of regret. I&amp;rsquo;m moving countries, schools and jobs and leaving those valuable connections behind, while people who stay in their neighbourhood are easily bound to their friends by time and proximity.&lt;/p&gt;</description><author>Saeed Esmaili</author><pubDate>Mon, 12 Aug 2024 12:49:59 GMT</pubDate><guid isPermaLink="true">https://saeedesmaili.com/notes/i-have-two-friends-an-introverts-guide-to-not-chasing-friendships/</guid></item><item><title>Air-powered logic circuits for error detection in pneumatic systems</title><link>http://groverlab.org/news/2024/08/12/parity-bit.html</link><description>&lt;p&gt;&lt;img alt="" src="/assets/ipc-device.jpg" /&gt;&lt;/p&gt;

&lt;p&gt;Shane Hoang’s work on using pneumatic logic to calculate parity bits and detect errors in air-powered systems was &lt;a href="https://www.sciencedirect.com/science/article/pii/S2666998624004071"&gt;published in &lt;em&gt;Device&lt;/em&gt;&lt;/a&gt;!  Special thanks to our coauthors Mabel Shehada, Zinal Patel, Minh-Huy Tran, Konstantinos Karydis, and Philip Brisk.&lt;/p&gt;</description><author>Grover Lab</author><pubDate>Mon, 12 Aug 2024 12:00:00 GMT</pubDate><guid isPermaLink="true">http://groverlab.org/news/2024/08/12/parity-bit.html</guid></item><item><title>Ranking the Hugo and Nebula Award Winners and Nominees</title><link>https://blog.nawaz.org/posts/2024/Aug/ranking-the-hugo-and-nebula-award-winners-and-nominees/</link><description>&lt;p&gt;I grew up reading science fiction. Although I enjoyed the genre a great
deal, most of my reading was confined to Isaac Asimov and Arthur C.
Clarke (with a dash of Ray&amp;nbsp;Bradbury).&lt;/p&gt;
&lt;p&gt;By the end of my college years, I had read most of the fiction from
those two …&lt;/p&gt;</description><author>Beetle Space</author><pubDate>Mon, 12 Aug 2024 10:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.nawaz.org/posts/2024/Aug/ranking-the-hugo-and-nebula-award-winners-and-nominees/</guid></item><item><title>Using ESPHome Without the Home Assistant Addon</title><link>https://www.petekeen.net/esphome-without-the-addon/</link><description>&lt;p&gt;The &amp;quot;blessed&amp;quot; flow for using ESPHome is the Home Assistant ESPHome Addon.
        This works well!
        It has a nice editor and it takes care of some housekeeping tasks for you.
        If you don't already have a comfortable development workflow it's a very nice way to start.&lt;/p&gt;
        &lt;p&gt;If you &lt;em&gt;do&lt;/em&gt; already have a working style that doesn't involve a web UI and a browser editor you can still use ESPHome, you just have to handle those housekeeping tasks yourself.&lt;/p&gt;
        &lt;h2 id="fundamentals" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-without-the-addon/#fundamentals"&gt;Fundamentals&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;I think it can be helpful to step back and take a look at the fundamentals of a piece of software like ESPHome before diving headlong into the deep pool of non-standard workflows.&lt;/p&gt;
        &lt;p&gt;At it's base level ESPHome is a microcontroller firmware generator.
        That is, it reads your YAML config, generates a bunch of C++ files and config files, and then using a compiler and some helper programs it generates a binary program that your microcontroller (usually but not always an ESP32) can run.&lt;/p&gt;
        &lt;p&gt;ESPHome also has a few very useful helpers.
        First, it can do seamless over the air (OTA) updates once any ESPHome firmware has been installed on a device.&lt;/p&gt;
        &lt;p&gt;Second, it has a pretty powerful web-based UI and configuration editor.&lt;/p&gt;
        &lt;p&gt;Third, ESPHome ships with a &amp;quot;native&amp;quot; binary protocol it can use to talk to Home Assistant (&lt;a href="https://www.petekeen.net/esphome-server-in-python"&gt;previously&lt;/a&gt;) complete with Noise-based symmetric key encryption.&lt;/p&gt;
        &lt;p&gt;Lastly, it can be used as a Home Assistant addon, which as I said earlier takes care of a few things for you.
        The OTA update functionality requires a pre-shared key to validate that updates are coming from a known source.
        The addon takes care of generating that and the Noise secret and sharing these keys with Home Assistant so you don't have to care about them.&lt;/p&gt;
        &lt;h2 id="esphome-on-a-macbook%3F!" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-without-the-addon/#esphome-on-a-macbook%3F!"&gt;ESPHome on a Macbook?!&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;My ESPHome workflow doesn't involve the web UI or the addon at all.
        Instead, I install ESPHome on my Macbook with &lt;code&gt;homebrew&lt;/code&gt; and manage the OTA and HA secret keys with 1Password and a small helper script.
        The script and all of my ESPHome configs live in &lt;a href="https://github.com/keenfamily-us/esphome-configs/tree/main"&gt;this public GitHub repo&lt;/a&gt;.&lt;/p&gt;
        &lt;p&gt;This is the script as it exists today:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-bash"&gt;#!/bin/bash
        
        set -x
        set -eo pipefail
        
        trap &amp;quot;rm common/device_base.yaml&amp;quot; EXIT
        
        op inject --in-file common/device_base.yaml.in --out-file common/device_base.yaml
        
        command=$1
        shift
        
        if [ $# -eq 0 ]; then
        configs=&amp;quot;*.yaml&amp;quot;
        else
        configs=&amp;quot;$@&amp;quot;
        fi
        
        esphome $command $configs
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;All this is really doing is using 1Password's &lt;code&gt;op inject&lt;/code&gt; tool to generate a file with my configured secrets, runs &lt;code&gt;esphome&lt;/code&gt;, and makes sure to clean up the generated file with that &lt;code&gt;trap&lt;/code&gt; line.
        The top of &lt;code&gt;device_base.yaml.in&lt;/code&gt; looks like this:&lt;/p&gt;
        &lt;pre&gt;&lt;code class="language-yaml"&gt;substitutions:
        wifi_ssid: &amp;quot;op://keen.land secrets/ESPHome Secrets/ESPHOME_WIFI_SSID&amp;quot;
        wifi_password: &amp;quot;op://keen.land secrets/ESPHome Secrets/ESPHOME_WIFI_PASSWORD&amp;quot;
        fallback_ssid_password: &amp;quot;op://keen.land secrets/ESPHome Secrets/ESPHOME_FALLBACK_SSID_PASSWORD&amp;quot;
        home_assistant_encryption_key: &amp;quot;op://keen.land secrets/ESPHome Secrets/ESPHOME_HOME_ASSISTANT_ENCRYPTION_KEY&amp;quot;
        ota_password: &amp;quot;op://keen.land secrets/ESPHome Secrets/ESPHOME_OTA_PASSWORD&amp;quot;
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;All of those are just &lt;code&gt;text&lt;/code&gt; entries in the &lt;code&gt;ESPHome Secrets&lt;/code&gt; rich text item, but again they can be whatever you want.
        If you decide to make them &lt;code&gt;password&lt;/code&gt; type entries I believe you'd need to add &lt;code&gt;--reveal&lt;/code&gt; to the &lt;code&gt;op inject&lt;/code&gt; command, but I'm not 100% certain on that.&lt;/p&gt;
        &lt;p&gt;This differs in kind of a fundamental way from the way the web UI / addon work, in so far as the addon will create and manage &lt;em&gt;unique&lt;/em&gt; OTA and HA keys for each device.
        My setup instead uses two keys shared among all of my devices.
        I don't see this as a significant risk because I don't use esphome devices in higher security contexts (i.e. my door locks are not running esphome), but your threat model is likely different than mine so you should make your own decisions.
        Nothing is stopping you from using unique keys for every device with this setup, you just have more secrets to manage in 1Password.&lt;/p&gt;
        &lt;h2 id="workflow" tabindex="-1"&gt;&lt;a class="header-anchor" href="https://www.petekeen.net/esphome-without-the-addon/#workflow"&gt;Workflow&lt;/a&gt;&lt;/h2&gt;
        &lt;p&gt;My workflow looks like:&lt;/p&gt;
        &lt;pre&gt;&lt;code&gt;$ &amp;lt;edit whatever.yaml&amp;gt;
        $ ./build.sh run whatever.yaml
        # a bunch of compiler output and then logging from the device itself
        $ git add whatever.yaml &amp;amp;&amp;amp; git commit -m 'updates' &amp;amp;&amp;amp; git push origin main
        &lt;/code&gt;&lt;/pre&gt;
        &lt;p&gt;There aren't many hard edges with this setup.
        You can put whatever you want into &lt;code&gt;common&lt;/code&gt; and you can organize your devices however you want.&lt;/p&gt;
        &lt;p&gt;One exception is that &lt;code&gt;secrets.yaml&lt;/code&gt; has some confusing implicit behavior, so I just commit an empty one and use a different file for my secrets.&lt;/p&gt;
        &lt;p&gt;Updating ESPHome is not something I do on a regular basis, but when I do it's basically just &lt;code&gt;brew upgrade esphome &amp;amp;&amp;amp; ./build.sh&lt;/code&gt;.&lt;/p&gt;
        &lt;p&gt;The process to add a new ESPHome device to Home Assistant is also fairly streamlined.
        All you do is attach the ESP32 device to your computer with USB, create a new yaml file, and run &lt;code&gt;./build.sh run &amp;lt;your new file&amp;gt;.yaml&lt;/code&gt;.
        ESPHome will pick up that there's a serially attached device without firmware and handle flashing the new firmware to it.&lt;/p&gt;
        &lt;p&gt;Once ESPHome is running on the new device it should show up in the Home Assistant integrations page and something that can be added.
        Clicking the accept button will open a config flow where you can paste your Home Assistant key, and then it should work just like any other device.&lt;/p&gt;</description><author>Pete Keen</author><pubDate>Mon, 12 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.petekeen.net/esphome-without-the-addon/</guid></item><item><title>PrizePicks: The Early Days</title><link>https://solomon.io/prizepicks-early-days/</link><description>10 years ago Adam Wexler, Tareq Dowla and Sam Solomon founded SidePrize, the fantasy sports company that most know today as PrizePicks.</description><author>Sam Solomon</author><pubDate>Mon, 12 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://solomon.io/prizepicks-early-days/</guid></item><item><title>You should make a new programming language</title><link>https://ntietz.com/blog/you-should-make-a-new-terrible-programming-language/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;Every software engineer uses a programming language, usually multiple.
Few of us &lt;em&gt;make&lt;/em&gt; programming languages.
This makes sense, because the work we need to get done can typically be done just fine in the languages that exist.
Those already have people making them better.
Let's focus on the task at hand.&lt;/p&gt;
&lt;p&gt;But that means that we're missing out on some learning opportunities.
I stumbled into those when I &lt;a href="https://ntietz.com/blog/introducing-hurl/"&gt;made a language&lt;/a&gt; based on a silly premise: control flow via exceptions and &lt;em&gt;nothing else&lt;/em&gt;.
It was done as a joke, but I accidentally learned things along the way.&lt;/p&gt;
&lt;h1 id="it-s-special-that-we-make-our-own-tools"&gt;It's special that we make our own tools&lt;/h1&gt;
&lt;p&gt;Every serious woodworker makes some of their own equipment.
Some will make their workbench, maybe sawhorses, perhaps jigs for myriad tools and work setups.
These are things a woodworker can make from wood.
But we don't often have access to the machines we'd need for making &lt;em&gt;all&lt;/em&gt; the tools we use:
You'd need a metalworking shop to make portions of chisels and planes, let alone any power tools we use.&lt;/p&gt;
&lt;p&gt;As programmers, we're in a different position.
We have near total control over the machine, and we have the capability, in theory, to build everything from scratch&lt;sup class="footnote-reference" id="fr-firmware-sad-1"&gt;&lt;a href="https://ntietz.com/blog/you-should-make-a-new-terrible-programming-language/#fn-firmware-sad"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.
Since the tools we use are all software-based, and we write software, we can create all of our own tools, from the operating system on up.&lt;/p&gt;
&lt;p&gt;This is a privilege which few fields enjoy.
The closest other one I can think of is that machinists can likely produce a lot of their own tools, too.
Where we assume that CPUs and RAM exist, they can assume that motors and control boards exist.
Then they can build those into the rest of the tool.
And so, like machinists, we're able to get incredibly close to our tools.&lt;/p&gt;
&lt;h1 id="what-you-learn-by-making-a-language"&gt;What you learn by making a language&lt;/h1&gt;
&lt;p&gt;One of the tools we interact with the most is the programming language.
We use one to get any programming work done, and they shape how we think through problems as well.
You use a programming language as a tool of thought even when you're away from the keyboard.
This makes it ripe for learning.
You will learn a &lt;em&gt;lot&lt;/em&gt; if you make a new programming language.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You'll learn about grammars and language design.&lt;/strong&gt;
Before you can implement a programming language, you'll have to decide what you even want it to &lt;em&gt;be&lt;/em&gt;.
Is this an imperative language, or functional, or something else?
Is it object oriented?
Does it have traditional syntax borrowed from another language, or are you doing something new and weird?
These, and many others, are the questions you'll grapple with in designing a language.&lt;/p&gt;
&lt;p&gt;In the process, you'll learn about &lt;em&gt;why&lt;/em&gt; other languages are designed the way they are.
If you're lucky, you'll learn some of this in the initial design process.
For example, while working on my next language, Lilac, I learned why &lt;a href="https://ntietz.com/blog/researching-why-we-use-semicolons-as-statement-terminators/"&gt;semicolons are so common&lt;/a&gt; because I tried picking something else.
Discussing it with a friend uncovered a &lt;em&gt;lot&lt;/em&gt; of potential drawbacks in other choices!
If you're less lucky, you'll learn those lessons in the implementation phase, and those lessons will really stick.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You'll learn about parsing.&lt;/strong&gt;
This is one of the first things you'll run into when you start to implement your language.
You can't do a whole lot else without parsing the language.
To start writing the parser, you'll have to pick what kind of parser to write.
Don't overthink it when you're just starting out.
Although, if you're really interested in parsers, it can be a wonderful topic to dive deep into.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;You'll learn about runtime execution.&lt;/strong&gt;
Running your code means you have to write the runtime (or the compiler) which means thinking deeply about &lt;em&gt;how&lt;/em&gt; it will work at run time.
When an exception is thrown, how does that actually work?
When you reference a variable, how do you know which memory location to find it in?
If you run a recursive function, is there a limit to how far you can recurse? Why is that?
These are some of the questions you'll answer.&lt;/p&gt;
&lt;p&gt;The list really goes on, and on, and on.
You can tailor your language to what you want to learn about.
My first language, Hurl, taught me about the basics of making an interpreter, designing a language, and writing a grammar.
My second language, Lilac, is going to teach me more about type systems, runtimes, and instrumentation.&lt;/p&gt;
&lt;p&gt;As you go make a language, you'll gain deeper intuitions for and understanding of other languages.
When I implemented Hurl and ran into parsing errors, it would spit out raw token names at me.
This resembled some of the errors I used to see sometimes in my Neovim Rust LSP integration, and it started to make &lt;em&gt;those&lt;/em&gt; errors easier to understand.
Each language and implementation decision you make will deepen your understanding of the languages you use, and you'll be a better user for it.&lt;/p&gt;
&lt;h1 id="it-will-be-a-bad-language-and-that-s-okay"&gt;It will be a bad language, and that's okay&lt;/h1&gt;
&lt;p&gt;The nice thing with writing your own language for learning is that it's likely to be a bad one.
It's certainly &lt;em&gt;possible&lt;/em&gt; to make &lt;a href="https://gleam.run/"&gt;new, good languages&lt;/a&gt;, and that's wonderful!
But in my experience, it's best to separate out learning how to do something from doing it exceptionally well.&lt;/p&gt;
&lt;p&gt;When you go into it knowing that it's going to be a bad language, it can be very freeing!
Bad doesn't mean that it's &lt;em&gt;not useful to you&lt;/em&gt;, because it still can be.
Mostly, it means that it will lack the fit and finish of a "real" language and it will be defective in some way that limits widespread use.
But you can make something that solves a specific problem for you, lets you do Advent of Code puzzles, or earns you nerd cred with your friends.
These are useful things.&lt;/p&gt;
&lt;p&gt;Since you aren't going to make the next Python, you can focus on the things that are interesting, compelling, and fruitful for learning.
You can slough off all the things that are tedious but necessary for real-world usage.
Your learning can be targeted and you can keep it fun, so you're more likely to finish the project.
And it's okay to break things arbitrarily, or make wildly ridiculous language choices that just make you smile.
Because hey, it's going to be bad &lt;em&gt;anyway&lt;/em&gt;, right?&lt;/p&gt;
&lt;h1 id="getting-started-making-languages"&gt;Getting started making languages&lt;/h1&gt;
&lt;p&gt;It's intimidating to sit down in front of a blank editor and "make a new language."
For a long time, I thought—even as Principal Software Engineer—that it was some dark art that is beyond my abilities.
That's a load of crock, and &lt;em&gt;all of us&lt;/em&gt; programmers can do it.
It gets easier every year to get started, because there are so many resources out there to learn from.&lt;/p&gt;
&lt;p&gt;The first thing I'd recommend is implementing someone &lt;em&gt;else's&lt;/em&gt; language in a guided fashion.
I followed &lt;a href="https://craftinginterpreters.com/"&gt;Crafting Interpreters&lt;/a&gt; for this, and it's &lt;em&gt;incredible&lt;/em&gt;.
I've also heard good things about &lt;a href="https://interpreterbook.com/"&gt;Writing An Interpreter In Go&lt;/a&gt; and &lt;a href="https://www.buildyourownlisp.com/"&gt;Build Your Own Lisp&lt;/a&gt;.
Any of these will give you a taste of how languages work and let someone experienced guide you thorough it.&lt;/p&gt;
&lt;p&gt;One thing, though: I've found it is a good idea to choose a &lt;em&gt;different&lt;/em&gt; implementation language from what the book uses.
Crafting Interpreters uses Java and C, so I used Rust.
By choosing a different language, you're forced to grapple with the concepts to translate them.
You can't simply retype the code, so you will learn it at a deeper level.&lt;/p&gt;
&lt;p&gt;After that, the direction you go is really up to you.
I got started with Hurl by just kind of designing it and throwing things at the wall to see what sticks.
That worked and let me crystallize a lot of the knowledge I got from Crafting Interpreters.
For Lilac, I've read &lt;a href="https://www3.nd.edu/~dthain/compilerbook/"&gt;one book&lt;/a&gt; so far and have a short list of others to read.
When I asked friends for recommendations, these are a few of the books they recommend for this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www3.nd.edu/~dthain/compilerbook/"&gt;Introduction to Compilers and Language Design&lt;/a&gt;, which I've read and really enjoyed&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shop.elsevier.com/books/engineering-a-compiler/cooper/978-0-12-815412-0"&gt;Engineering a Compiler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.plai.org/"&gt;Programming Languages: Application and Interpretation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://suif.stanford.edu/dragonbook/"&gt;Compilers: Principles, Techniques, and Tools&lt;/a&gt; aka the Dragon Book&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What you read will depend on where you want to go next and what you want to learn.&lt;/p&gt;
&lt;h1 id="go-forth-make-something-fun"&gt;Go Forth, make something fun&lt;/h1&gt;
&lt;p&gt;I think we should all go and make a new language.
It's a great way to learn, and new ideas have to come from somewhere.
At the end of the day, it's a wonderful way to have some fun with your computer.&lt;/p&gt;
&lt;p&gt;Oh, and &lt;em&gt;please&lt;/em&gt; expand the vocabulary of programming language names.
We can say "Go Forth" but it's hard to put together a whole sentence with just programming languages.
Let's fix that, shall we?
And let's B Swift about it.&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-firmware-sad"&gt;
&lt;p&gt;There are some firmware blobs which we &lt;em&gt;don't&lt;/em&gt; control.
But there is fully open hardware, and you have to stop going down the stack somewhere.
Well, I guess you &lt;em&gt;could&lt;/em&gt; go start a mining operation to extract ore from the earth and go truly from scratch... &lt;a href="https://ntietz.com/blog/you-should-make-a-new-terrible-programming-language/#fr-firmware-sad-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 12 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/you-should-make-a-new-terrible-programming-language/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>Debugging careers: why I'm opening up The SWaits Code</title><link>https://swaits.com/announcing-the-swaits-code/</link><description>&lt;p&gt;I've been in tech for over 30 years now. Principal Engineer at Amazon. Seen a
lot, screwed up plenty, learned even more. Along the way, people started calling
me SWaits, and I realized I had developed a sort of playbook. A set of
principles and practices that have guided my career. I call it &lt;em&gt;The SWaits Code&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Now, I'm doing something new. I'm opening up that code for others to see.&lt;/p&gt;
&lt;p&gt;Why? It's pretty straightforward. I want to help. No ulterior motives, no hidden
agenda. Just sharing what I've learned.&lt;/p&gt;
&lt;h3 id="the-impact-of-mentorship"&gt;The Impact of Mentorship&lt;/h3&gt;
&lt;p&gt;Over the years, I've had the privilege of directly mentoring hundreds of
technologists. And through various indirect means - like giving talks, writing
internal documents, and influencing processes - that impact has extended to
thousands more. I built relationships with countless technologists who
consistently turn to me for advice on career growth, performance issues,
promotions, and navigating conflicts. Sometimes, we even touch on personal
stuff.&lt;/p&gt;
&lt;p&gt;Each time I've helped someone work through a career challenge, I've learned
something too. It's always been a two-way street, and now it feels right to open
up that exchange to a broader audience.&lt;/p&gt;
&lt;h3 id="why-now"&gt;Why Now?&lt;/h3&gt;
&lt;p&gt;Look, I've been comfortable in my Amazon bubble. It's been a great experience.
But I know the tech industry is bigger than any one company. The challenges, the
opportunities, the pitfalls – they're pretty universal.&lt;/p&gt;
&lt;p&gt;When acknowledging to myself that I've been at this a long time and had a
moderately successful career, I realized that I probably do have something
unique and valuable to offer the world. Then, I started asking myself: "Why keep
all this experience bottled up within one company? Why not try to help people
across the entire tech landscape?"&lt;/p&gt;
&lt;p&gt;I decided to take a big leap of faith and give something new a try.&lt;/p&gt;
&lt;p&gt;That's what led to this livestream idea. &lt;a href="https://www.linkedin.com/events/7227000655784108035/about/" rel="external"&gt;The SWaits Code: Debug your Career -
Live Q&amp;amp;A&lt;/a&gt; isn't some
flashy event title. It's just me, in an officer-hours sort of format, opening up
about what I've learned, hoping it might help others navigate their own paths.&lt;/p&gt;
&lt;h3 id="what-to-expect"&gt;What to Expect&lt;/h3&gt;
&lt;p&gt;This isn't going to be a polished presentation or a motivational speech. It's
just going to be real talk about life in tech. We might get into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Approaches to those technical problems that make you want to bang your head against the wall&lt;/li&gt;
&lt;li&gt;The realities of moving into leadership, or even management&lt;/li&gt;
&lt;li&gt;How to navigate big tech environments without losing yourself&lt;/li&gt;
&lt;li&gt;Finding a balance between coding passion and career growth&lt;/li&gt;
&lt;li&gt;Learning from mistakes (I've made more than my fair share)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="why-this-might-be-useful"&gt;Why This Might Be Useful&lt;/h3&gt;
&lt;p&gt;I'm not claiming to have all the answers. What I am offering is a look behind
the curtain. The good, the bad, the ugly – all of it. If you're new to tech,
maybe you'll find some shortcuts to avoid common pitfalls. If you're a veteran,
perhaps you'll discover a new perspective on old problems, or teach me something
new!&lt;/p&gt;
&lt;h3 id="join-in"&gt;Join In&lt;/h3&gt;
&lt;p&gt;This Friday, I'm putting it all out there. No script, no rehearsed answers. Ask
me anything. Challenge my ideas. Disagree with me. Let's have a conversation,
that's how we all grow!&lt;/p&gt;
&lt;p&gt;If you're interested, you can submit questions now or during the stream. Use
event 12769 at &lt;a href="https://onlinequestions.org/" rel="external"&gt;onlinequestions.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Let's work through some career challenges together. Hope to see you there.&lt;/p&gt;</description><author>swaits.com</author><pubDate>Mon, 12 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://swaits.com/announcing-the-swaits-code/</guid></item><item><title>Two years later: reflections on my return to Amazon</title><link>https://swaits.com/reflecting-on-returning-to-amazon/</link><description>&lt;p&gt;It's been two years since I &lt;a href="https://swaits.com/why-i-came-back-to-amazon"&gt;wrote about my return to
Amazon&lt;/a&gt;. A lot has happened,
both in the world and in my career. Time for an update.&lt;/p&gt;
&lt;h2 id="leadership-principles-still-the-backbone"&gt;Leadership Principles: Still the Backbone&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://www.amazon.jobs/content/en/our-workplace/leadership-principles" rel="external"&gt;Leadership Principles (LPs)&lt;/a&gt; continue to be the backbone of how we operate at
Amazon. My experience with them hasn't changed much - they're still guiding our
decisions and actions daily. I won't bore you with a list of all the LPs, but
they remain as relevant as ever.&lt;/p&gt;
&lt;h2 id="deep-dive-into-operations-and-robotics"&gt;Deep Dive into Operations and Robotics&lt;/h2&gt;
&lt;p&gt;My time in Operations and Robotics was incredibly enlightening. I got to dive
deep into large-scale federated simulation, which is a personal passion of mine.
The technology enabling our logistics network is mind-blowing - I saw and
learned about innovations that are truly pushing the boundaries of what's
possible.&lt;/p&gt;
&lt;h2 id="stability-in-turbulent-times"&gt;Stability in Turbulent Times&lt;/h2&gt;
&lt;p&gt;Personally, I've experienced good stability at Amazon. But I'd be remiss if I
didn't acknowledge that this isn't universal. The tech industry as a whole has
faced turbulence in recent years, and Amazon hasn't been immune.&lt;/p&gt;
&lt;p&gt;I'm thankful for my job and the autonomy I have within it. But I also recognize
this as a privilege - one that I've earned through decades of hard work, but a
privilege nonetheless. It's a bittersweet realization, knowing that not everyone
has been as fortunate.&lt;/p&gt;
&lt;h2 id="new-opportunities-and-lateral-mobility"&gt;New Opportunities and Lateral Mobility&lt;/h2&gt;
&lt;p&gt;One of the things I love most about Amazon is the opportunity for lateral
mobility. Recently, I made a significant move from Operations and Robotics to
the US Prime and Marketing Tech team. It wasn't an easy decision - some of the
leaders who initially attracted me to Operations and Robotics had moved on, and
while there were still phenomenal leaders in that space, an incredible
opportunity arose that I couldn't pass up.&lt;/p&gt;
&lt;p&gt;Now, I'm reporting to a VP with an SVP as my skip-level manager. In terms of
scope and responsibility, it's the biggest challenge I've taken on in my career.
It's a bit scary, but incredibly invigorating.&lt;/p&gt;
&lt;p&gt;This kind of mobility is a powerful tool at Amazon. It keeps managers
accountable - you need to work hard to keep your employees challenged, happy,
and fulfilled. If you don't, they'll find a manager who does.&lt;/p&gt;
&lt;h2 id="work-life-balance-and-autonomy"&gt;Work-Life Balance and Autonomy&lt;/h2&gt;
&lt;p&gt;My work-life balance has remained steady throughout these two years. The
return-to-office mandates haven't affected me, which I recognize is another
aspect of the high demand for Principal Engineers.&lt;/p&gt;
&lt;p&gt;As for autonomy, it's been consistently high throughout my time here. If
anything, in my new role, it's as high as it's ever been.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;Closing Thoughts&lt;/h2&gt;
&lt;p&gt;Two years after my return, I can say that coming back to Amazon was the right
choice for me. The opportunity to work on cutting-edge technology, the ability
to move between teams, and the consistent autonomy in my work have all been
incredibly rewarding.&lt;/p&gt;
&lt;p&gt;At the same time, I'm acutely aware of the challenges and changes in the broader
tech industry. It's a complex landscape, and while my personal experience has
been positive, I know that's not universal.&lt;/p&gt;
&lt;p&gt;As I look ahead to the next chapter in my career at Amazon, I'm excited about
the challenges and opportunities that lie ahead. The scope of my new role is
daunting, but it's exactly the kind of challenge I thrive on.&lt;/p&gt;
&lt;p&gt;Here's to continuous learning, growth, and making an impact - wherever your
career takes you.&lt;/p&gt;</description><author>swaits.com</author><pubDate>Mon, 12 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://swaits.com/reflecting-on-returning-to-amazon/</guid></item><item><title>Note 143</title><link>https://qubyte.codes/notes/1723368452578</link><description>&lt;p&gt;Back in Hitachino Brewing Lab in the old Manseibashi Station building, just outside Akiba&lt;/p&gt;

    &lt;img alt="A half drained glass of session IPA, on a counter with a built-in strip lamp against a red brick wall. The counter is varnished wood." src="https://qubyte.codes/images/1723368292861.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Sun, 11 Aug 2024 12:27:32 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1723368452578</guid></item><item><title>Python setup tools</title><link>https://www.ashish.zip/2024/08/python-setup-tools.html</link><description>&lt;p&gt;&amp;nbsp;Python is a very popular programming language. It is somewhat easier to read than other programming languages. Best 2nd choice for many tasks that includes data analysis and plotting. R is amazing for my use case and I feel it is the better choice especially when it comes to statistical test and plotting. Ggplot2 is nearly unrivaled for the ergonomics.&amp;nbsp;&lt;/p&gt;&lt;p&gt;However, the whenever I am determined to use Python, I am overwhelmed by the setup process. &amp;nbsp;This is focused on the setup in the MacOS world. &amp;nbsp;( I won't go over Conda at all since for some reason it has sowed more confusion).&amp;nbsp;&lt;/p&gt;&lt;p&gt;This is a draft version of the post which will get updated as I learn more.&amp;nbsp;&lt;/p&gt;&lt;p&gt;1. &lt;b&gt;Python Version:&amp;nbsp;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;2. &lt;b&gt;Pip (Official way to install)&lt;/b&gt;&lt;/p&gt;&lt;p&gt;3. &lt;b&gt;venv&lt;/b&gt; (Comes with the Python 3 version)&lt;/p&gt;&lt;p&gt;4. &lt;b&gt;UV &lt;/b&gt;(Not platform agnostic as Poetry) : This is more for resolving package dependency as compared to pip. So far it has worked good for me which means&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp; 1. Creating virtual&amp;nbsp;&lt;/span&gt;environment:&lt;/p&gt;&lt;p&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;span style="font-family: Inconsolata;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="font-size: 17px;"&gt;&lt;span style="font-family: Inconsolata;"&gt;uv venv uenv1&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size: 17px;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;span&gt;&amp;nbsp; 2. Activating virtual environment&lt;/span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size: 17px;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="font-size: 17px;"&gt;&lt;span style="font-family: Inconsolata;"&gt;source uenv1/bin/activate&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size: 17px;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;3. Installing your programs&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size: 17px;"&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="font-family: Inconsolata;"&gt;&lt;span style="font-size: 17px;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="font-size: 17px;"&gt;uv pip install pandas&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-size: 17px;"&gt;&lt;span style="font-family: inherit;"&gt;Vscode should detect the folder within which we have installed this virtual environment.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;5. &lt;b&gt;Pipx&lt;/b&gt;&lt;/p&gt;&lt;p&gt;&lt;span style="font-family: inherit;"&gt;Got pipx installed using &lt;/span&gt;&lt;span style="font-family: Inconsolata;"&gt;brew install pipx&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; and then was able to install uv.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span&gt;&amp;nbsp;Pipx is supposed to be subset of the pip which means it can only install command line tools but not all&lt;span&gt;&amp;nbsp;&lt;/span&gt;the packages such as Pandas or Polars which need to be imported as library. &amp;nbsp;This is aptly described at:&amp;nbsp;&lt;/span&gt;&lt;a href="https://pipx.pypa.io/stable/comparisons/"&gt;https://pipx.pypa.io/stable/comparisons/&lt;/a&gt;&lt;/p&gt;&lt;p&gt;6.&lt;b&gt; Poetry&lt;/b&gt;&lt;/p&gt;&lt;p&gt;7. &lt;b&gt;PDM&lt;/b&gt;&lt;/p&gt;&lt;p&gt;8. PyPy&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description><author>Thoughts and ideas</author><pubDate>Sun, 11 Aug 2024 03:49:43 GMT</pubDate><guid isPermaLink="true">https://www.ashish.zip/2024/08/python-setup-tools.html</guid></item><item><title>Incorporating Ads into Large Language Models Outputs</title><link>https://blog.reachsumit.com/posts/2024/08/ads-llm/</link><description>&lt;div class="featured-image"&gt;
                &lt;img src="/posts/2024/08/ads-llm/featured-image-preview.webp" /&gt;
            &lt;/div&gt;This article provides an introduction to online advertising systems and explores research work that incorporates ads into the LLM responses to user queries of commercial nature.</description><author>Sumit's Diary</author><pubDate>Sun, 11 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://blog.reachsumit.com/posts/2024/08/ads-llm/</guid></item><item><title>Using the Sunset header with GraphQL</title><link>https://sophiabits.com/blog/using-the-sunset-header-with-graphql</link><description>&lt;p&gt;Try as we might to design APIs that last, it is &lt;em&gt;very&lt;/em&gt; difficult to completely avoid the need for breaking changes. The &lt;code&gt;Sunset&lt;/code&gt; HTTP header offers a standardized way to inform clients about the deprecation of API endpoints in a programmatically interpretable way. In my previous post on this topic I discussed what this header is and why it’s important for managing API lifecycles—but didn’t explain &lt;em&gt;how&lt;/em&gt; to use it.&lt;/p&gt;&lt;p&gt;I’ve since published a package called &lt;code&gt;graphql-sunset&lt;/code&gt; which lets you sunset parts of your schema with no code changes. Let’s take a look at how it can be used.&lt;/p&gt;&lt;p&gt;&lt;em&gt;Read more on &lt;a href="https://sophiabits.com/blog/using-the-sunset-header-with-graphql"&gt;sophiabits.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;</description><author>Sophia Willows' Blog</author><pubDate>Sun, 11 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://sophiabits.com/blog/using-the-sunset-header-with-graphql</guid></item><item><title>Using Docker to run Cypress with Maven</title><link>https://kinoshita.eti.br/2024/08/11/using-docker-to-run-cypress-with-maven.html</link><description>&lt;p&gt;Apache Jena runs Cypress tests from Maven, which makes running everything
containerized a bit more difficult. To make it more complicated, we also
used &lt;code&gt;wait-on&lt;/code&gt; and &lt;code&gt;concurrently&lt;/code&gt; to orchestrate how the tests and API
test process are launched.&lt;/p&gt;
&lt;p&gt;The solution found was to combine the &lt;a href="https://hub.docker.com/_/maven"&gt;official Maven docker image&lt;/a&gt;,
with the &lt;a href="https://hub.docker.com/r/cypress/included"&gt;&lt;code&gt;cypress/included&lt;/code&gt; image&lt;/a&gt;,
in a multi-stage build.&lt;/p&gt;
&lt;div class="popout"&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-dockerfile"&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #177500;"&gt;# A multi-stage image with Cypress and Java+Maven for Jena... ALv2...&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# To build it:&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# `docker build -t jena/build:latest .`&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;#&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# To run it:&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# `docker run --entrypoint "" --rm -ti jena/build:latest /bin/bash`&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #a90d91;"&gt;FROM&lt;/span&gt;&lt;span style="color: #c41a16;"&gt; maven:3.9.8-eclipse-temurin-21-jammy AS maven&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# The Maven stage. Nothing to see here, we simply copy artefacts&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# from this stage onto the next one.&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# Docs: https://hub.docker.com/_/maven&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #a90d91;"&gt;FROM&lt;/span&gt;&lt;span style="color: #c41a16;"&gt; cypress/included:13.13.1&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# The image with Cypress and everything else included. Compatible&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# with temurin jammy, so we can just copy Maven and Java, and set&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# the $PATH.&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# Docs: https://hub.docker.com/r/cypress/included&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;#&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# NOTE: The Cypress image must match our Cypress version in package.json.&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# This is due to how Cypress loads the binary from the cache. It'll&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# expect a binary at `/root/.cache/Cypress/$version/Cypress/`. With&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #177500;"&gt;# the `$version` coming from the version from the package.json file.&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #a90d91;"&gt;COPY&lt;/span&gt; --from&lt;span style="color: #000;"&gt;=&lt;/span&gt;maven /usr/share/maven/ /usr/share/maven/ &lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #a90d91;"&gt;COPY&lt;/span&gt; --from&lt;span style="color: #000;"&gt;=&lt;/span&gt;maven /opt/java/ /opt/java&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #a90d91;"&gt;ENV&lt;/span&gt; &lt;span style="color: #000;"&gt;PATH&lt;/span&gt;&lt;span style="color: #000;"&gt;=&lt;/span&gt;&lt;span style="color: #c41a16;"&gt;"/usr/share/maven/bin:/opt/java/openjdk/bin:&lt;/span&gt;&lt;span style="color: #000;"&gt;$PATH&lt;/span&gt;&lt;span style="color: #c41a16;"&gt;"&lt;/span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #a90d91;"&gt;ENTRYPOINT&lt;/span&gt; [&lt;span style="color: #c41a16;"&gt;""&lt;/span&gt;]&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display: flex;"&gt;&lt;span&gt;&lt;span style="color: #000;"&gt;&lt;/span&gt;&lt;span style="color: #a90d91;"&gt;CMD&lt;/span&gt; [&lt;span style="color: #c41a16;"&gt;"mvn"&lt;/span&gt;]&lt;span style="color: #000;"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Using the image created with the &lt;code&gt;Dockerfile&lt;/code&gt; above, one can test Jena
Fuseki UI with containers with:&lt;/p&gt;</description><author>About on kinow</author><pubDate>Sun, 11 Aug 2024 00:02:52 GMT</pubDate><guid isPermaLink="true">https://kinoshita.eti.br/2024/08/11/using-docker-to-run-cypress-with-maven.html</guid></item><item><title>Privacy policy</title><link>https://seirdy.one/meta/privacy/</link><description>Privacy policy for seirdy.one</description><author>All content on Seirdy’s Home</author><pubDate>Sat, 10 Aug 2024 06:54:40 GMT</pubDate><guid isPermaLink="true">https://seirdy.one/meta/privacy/</guid></item><item><title>Note 142</title><link>https://qubyte.codes/notes/1723257417216</link><description>&lt;p&gt;I’m back at Glitch Coffee in 神保町. Delicious.&lt;/p&gt;

    &lt;img alt="Black coffee served in a small black cup and a small fluted jug, both upon a black slab with a card with tasting notes on." src="https://qubyte.codes/images/1723257215084.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Sat, 10 Aug 2024 05:36:57 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1723257417216</guid></item><item><title>MicroGrad.jl: Part 2 Automation with expressions</title><link>https://liorsinai.github.io/machine-learning/2024/08/03/micrograd-2-expr.html</link><description>A series on automatic differentiation in Julia. Part 2 uses metaprogramming to generate a modified (primal) forward pass and to reverse differentiate it into a backward pass. This post uses an expression based approach which can be brittle. Part 3 develops a more robust approach for the same code using IRTools.jl.</description><author>Lior Sinai</author><pubDate>Sat, 10 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://liorsinai.github.io/machine-learning/2024/08/03/micrograd-2-expr.html</guid></item><item><title>2024-08-10</title><link>https://ho.dges.online/pictures/2024-08-10/</link><description/><author>ho.dges.online</author><pubDate>Sat, 10 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ho.dges.online/pictures/2024-08-10/</guid></item><item><title>Interesting Browser Engine Bugs, Part 2: On Video Conferencing</title><link>/development/2024/08/10/browser-engine-bugs-pt2.html</link><description>&lt;h3 id="other-posts-in-this-series"&gt;Other Posts in This Series&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;a href="/development/2024/06/13/browser-engine-bugs-pt1.html"&gt;Part 1: Test Timeouts&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;Part 2: On Video Conferencing (You’re here!)&lt;/li&gt;
  &lt;li&gt;&lt;a href="/development/2024/08/16/browser-engine-bugs-pt3.html"&gt;Part 3: &lt;code class="language-plaintext highlighter-rouge"&gt;&amp;lt;select&amp;gt;&lt;/code&gt;Tags Crash Chrome&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;hr /&gt;

&lt;p&gt;This one’s two for the price of one! Because they happened to affect a video conferencing we used, they were very visible and led to some interesting bug reports.&lt;/p&gt;

&lt;h2 id="upside-down-video-and-mac-video-reactions-where-they-arent-supposed-to-be"&gt;Upside-down video and Mac video Reactions where they aren’t supposed to be&lt;/h2&gt;

&lt;h3 id="background"&gt;Background&lt;/h3&gt;

&lt;p&gt;We use a 3rd party video conferencing service where I work. I’m not going to name it by name here as it’s not really relevant – just know it’s browser-based and built on WebRTC. &lt;a href="/development/2023/01/06/failures-common-sense.html"&gt;I’ve talked about using it in the past&lt;/a&gt;, ironically as it was behaving unexpectedly at the time. However, these cases were caused by the browser, and in one case, the OS itself!&lt;/p&gt;

&lt;p&gt;It was pretty funny responding to internal reports of these as they didn’t really cause major problems, they just happened to be highly visible. Our internal employees interface with external users through this conferencing service, so it’s critical to have things go smoothly.&lt;/p&gt;

&lt;h3 id="symptoms-begin"&gt;Symptoms Begin&lt;/h3&gt;

&lt;h4 id="mac-reactions"&gt;Mac Reactions&lt;/h4&gt;

&lt;p&gt;Last year, Apple released macOS Sonoma which included a feature called &lt;a href="https://support.apple.com/en-us/105117"&gt;Reactions&lt;/a&gt;. This, unbeknownst to us, was enabled by default when upgrading from the previous macOS version.&lt;/p&gt;

&lt;p&gt;I hope you can see where this is going.&lt;/p&gt;

&lt;p&gt;Our internal employees are running machines with managed OS upgrades. So, when IT pushed out the recommended Sonoma upgrade, it inadvertently enabled it for &lt;em&gt;every video stream from the machine&lt;/em&gt; (does it overlay it at the OS or hardware level??). This included our browser-based video conferencing.&lt;/p&gt;

&lt;p&gt;The effects were triggered by hand gestures – and there were lots of false positives.&lt;/p&gt;

&lt;p&gt;Luckily, we quickly found a way to turn it off, but the mental image of our support staff helping frustrated customers surrounded by a shower of confetti was pretty funny.&lt;/p&gt;

&lt;p&gt;&lt;img alt="macOS Sonoma confetti overlay" src="https://cdsassets.apple.com/live/7WUAS350/images/macos-sonoma-video-gesture-confetti-two-peace-signs.png" /&gt;&lt;/p&gt;

&lt;h4 id="upside-down-video"&gt;Upside-Down Video&lt;/h4&gt;

&lt;p&gt;This one happened also happened last year. As I mentioned our video conferencing service is browser-based and built on WebRTC. Our support staff interfaces it through Chrome (whose version is also managed by IT). This issue was also triggered by a rollout of a new Chrome version, but specifically was a problem with Chromium, the underlying browser engine.&lt;/p&gt;

&lt;p&gt;I’ve lost the bug report from Chromium at this point, but it was involved in a refactor for image scaling when rendering WebRTC video streams. Essentially, it assumed that the video would never be rendered except in an upright fashion.&lt;/p&gt;

&lt;p&gt;However, our conferencing software included a loopback stream to be able to see your own output stream. You know what I’m talking about on tools like Zoom, Teams, etc. – it’s the smaller box you see yourself in next to the main video.&lt;/p&gt;

&lt;p&gt;The software to render this in the browser critically &lt;em&gt;mirrored&lt;/em&gt; this image to provide a more intuitive way to interpret that video… but unfortunately caused problems when Chrome was updated on top of the buggy Chromium version. The end result was that this loopback stream was also vertically flipped! Luckily, since it was a loopback and not actually transmitted to end users, there was no real effect other than generating quite a few confused reports from our support staff.&lt;/p&gt;

&lt;p&gt;All in all, we have hardened our ability to roll back these managed software updates, hopefully mitigating this issue in the future.&lt;/p&gt;

&lt;p&gt;That’s all for now. Thanks for reading!&lt;/p&gt;</description><author>ty-porter</author><pubDate>Sat, 10 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">/development/2024/08/10/browser-engine-bugs-pt2.html</guid></item><item><title>git squashit squash-it squa-shit</title><link>https://jasonraimondi.com/posts/git-squashit-squash-it-squa-shit/</link><description>&lt;h1 id="git-squashit"&gt;Git Squashit&lt;/h1&gt;
&lt;p&gt;A bash/zsh helper function to squash multiple Git commits.&lt;/p&gt;
&lt;h2 id="definition"&gt;Definition&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;function&lt;/span&gt; squashit &lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  git reset --soft HEAD~&lt;span class="nv"&gt;$1&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  git commit --edit -m&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;git log --format&lt;span class="o"&gt;=&lt;/span&gt;%B --reverse HEAD..HEAD@&lt;span class="o"&gt;{&lt;/span&gt;1&lt;span class="o"&gt;}&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="usage"&gt;Usage&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;squashit &amp;lt;number_of_commits&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;number_of_commits&amp;gt;&lt;/code&gt;: Number of recent commits to squash&lt;/li&gt;
&lt;li&gt;Opens default text editor for commit message modification&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="note"&gt;Note&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Modifies Git history. Use cautiously.&lt;/li&gt;
&lt;li&gt;Best for combining small, &amp;lsquo;wip&amp;rsquo; commits into a meaningful one.&lt;/li&gt;
&lt;li&gt;Consider creating a backup branch.&lt;/li&gt;
&lt;/ul&gt;</description><author>Jason Raimondi on Software Engineer</author><pubDate>Fri, 09 Aug 2024 18:30:00 GMT</pubDate><guid isPermaLink="true">https://jasonraimondi.com/posts/git-squashit-squash-it-squa-shit/</guid></item><item><title>Bike 4</title><link>https://mfashby.net/posts/2024-08-09-bike/</link><description>I got a cargo bike, it's fab</description><author>Home on mfashby.net</author><pubDate>Fri, 09 Aug 2024 16:11:48 GMT</pubDate><guid isPermaLink="true">https://mfashby.net/posts/2024-08-09-bike/</guid></item><item><title>Come As You Are</title><link>https://bastian.rieck.me/blog/2024/come_as_you_are/</link><description>&lt;p&gt;I had a marvellous time at a &lt;a href="https://www.maths.ox.ac.uk/groups/topological-data-analysis/spires-2024"&gt;conference on topological data
analysis&lt;/a&gt;
in Oxford this week. Next to the amazing research discussions, I had the
opportunity to talk to the next generation of students and understand
what they are worrying about these days. After a couple of
conversations, a pattern emerged: Many students were worried about being
perceived as &amp;lsquo;unprofessional,&amp;rsquo; &amp;lsquo;goofy,&amp;rsquo; or not &amp;lsquo;serious&amp;rsquo; enough by
their advisers. I empathise with this feeling, so let me be clear about
everyone who works with me or wants to work with me: &lt;strong&gt;Come as you
are.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I do not come from a strong academic background myself, so I know how it
feels to not fit in. Do not worry about that. You can be who you are
with me and my collaborators. If you want to crack jokes in
presentations, go for it. If you prefer T-shirts from hacker conferences
to dress shirts, go for it. If you are the type to rather wear bespoke
shirts and Oxfords,&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; go for it.&lt;/p&gt;
&lt;p&gt;The only thing I ask of you is that you treat everyone else with respect
and that you are reliable in your communication.&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; Anything else is up
to you.&lt;/p&gt;
&lt;p&gt;Academia is stuffy enough in some places. &lt;strong&gt;Come as you are&amp;mdash;and bring
a breath of fresh air with you.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Never Brogues, obviously.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Let your &amp;lsquo;Yes&amp;rsquo; be a &amp;lsquo;Yes,&amp;rsquo; and your &amp;lsquo;No&amp;rsquo; be a &amp;lsquo;No.&amp;rsquo;&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Fri, 09 Aug 2024 15:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2024/come_as_you_are/</guid></item><item><title>Using OpenOCD with LLDB for Raspberry Pi Pico W on macOS</title><link>https://web.navan.dev/posts/2024-08-08-openocd-macos-lldb.html</link><description>Using LLDB with OpenCOD on macOS</description><author>Navan's Archive</author><pubDate>Thu, 08 Aug 2024 19:08:00 GMT</pubDate><guid isPermaLink="true">https://web.navan.dev/posts/2024-08-08-openocd-macos-lldb.html</guid></item><item><title>Don't ask someone to file a ticket</title><link>https://www.nothingeasyaboutthis.com/dont-ask-someone-to-file-a-ticket/</link><description>While ticketing systems are useful, pushing a person asking for help in real-time to an asynchronous system can be counterproductive. This approach can lead to delays and hinder team cohesion. Handling requests directly can enhance efficiency, relationships, and stress levels in the workplace.</description><author>Nothing Easy About This</author><pubDate>Thu, 08 Aug 2024 17:21:03 GMT</pubDate><guid isPermaLink="true">https://www.nothingeasyaboutthis.com/dont-ask-someone-to-file-a-ticket/</guid></item><item><title>📋process_engineer_jd.md</title><link>https://www.jskherman.com/gists/process_engineer_jd/</link><description>A job description for the role of Process Engineer in the O&amp;amp;G industry.</description><author>jskherman</author><pubDate>Thu, 08 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://www.jskherman.com/gists/process_engineer_jd/</guid></item><item><title>Hacking a Virtual Power Plant</title><link>https://rya.nc/vpp-hack.html</link><description>&lt;!-- included from `include/globals.rst` --&gt;
&lt;p&gt;I recently had solar panels and a battery storage system from
&lt;a class="external" href="https://givenergy.co.uk/"&gt;GivEnergy&lt;/a&gt; installed at my house. A major selling
point for me was that they have a local network API which can be used to monitor
and control everything without relying on their cloud services. My plan is to
set up
&lt;a class="external" href="https://www.home-assistant.io/"&gt;Home Assistant&lt;/a&gt;
and integrate it with that, but in the meantime, I decided to let it talk to
the cloud. I set up some scheduled charging, then started experimenting with
the API.&lt;/p&gt;
&lt;p&gt;The next evening, I had control over a &lt;a class="external" href="https://en.wikipedia.org/wiki/Virtual_power_plant"&gt;virtual power plant&lt;/a&gt; comprised
of tens of thousands of grid connected batteries.&lt;/p&gt;</description><author>rya.nc</author><pubDate>Wed, 07 Aug 2024 23:53:00 GMT</pubDate><guid isPermaLink="true">https://rya.nc/vpp-hack.html</guid></item><item><title>Two Years of Superb Owl</title><link>https://superbowl.substack.com/p/two-years-of-superb-owl</link><description>A thank you</description><author>Superb Owl</author><pubDate>Wed, 07 Aug 2024 18:57:10 GMT</pubDate><guid isPermaLink="true">https://superbowl.substack.com/p/two-years-of-superb-owl</guid></item><item><title>Disinformation – a feminist issue</title><link>https://numun.fund/disinformation-a-feminist-issue/</link><description>By Subha Wijesiriwardena While disinformation has become a much-talked about issue in social movements, as well as philanthropic and policymaking spaces, there&amp;#8217;s not much consensus on how we understand it or its impact, nor about which tactics make for the best solutions. Similarly, while there is some recent attention paid to the concept of &amp;#8220;gendered [&amp;#8230;]</description><author>Numun Fund</author><pubDate>Wed, 07 Aug 2024 12:54:41 GMT</pubDate><guid isPermaLink="true">https://numun.fund/disinformation-a-feminist-issue/</guid></item><item><title>Disinformation as hydra, and resourcing the complex responses needed</title><link>https://numun.fund/disinformation-as-hydra-and-resourcing-the-complex-responses-needed/</link><description>By Sam de Silva In late May, I participated in Numun Fund’s three-day Disinfo Co-Lab in Bangkok. I was working as a consultant with Luminate’s Asia team, and fortunately for me, I was the only one from the team who was free and available to attend. The three days I spent at the Co-Lab provided [&amp;#8230;]</description><author>Numun Fund</author><pubDate>Wed, 07 Aug 2024 12:51:31 GMT</pubDate><guid isPermaLink="true">https://numun.fund/disinformation-as-hydra-and-resourcing-the-complex-responses-needed/</guid></item><item><title>Collaborating against disinformation and toward the pluriverse</title><link>https://numun.fund/collaborating-against-disinformation-and-toward-the-pluriverse/</link><description>by Pamela Gloria Cajilig In a Philippine island that sits where the river meets the sea, a community of fisherfolk contemplate their future survival. Decades of poorly planned coastal development and governance have triggered a series of lingering, entangled, and totalizing flood disasters in this landscape that I have been visiting for the past four [&amp;#8230;]</description><author>Numun Fund</author><pubDate>Wed, 07 Aug 2024 12:43:40 GMT</pubDate><guid isPermaLink="true">https://numun.fund/collaborating-against-disinformation-and-toward-the-pluriverse/</guid></item><item><title>What I Learned at the Numun Disinfo Gathering</title><link>https://numun.fund/what-i-learned-at-the-numun-disinfo-gathering/</link><description>By Yan Naung Oak In May, I received an invitation in my inbox to a “gathering” in Bangkok by some trusted colleagues working in the disinformation space. I learned that it was organized by the Numun Fund, an initiative that supports feminist tech movements, particularly in the Global South. My team and I at Thibi [&amp;#8230;]</description><author>Numun Fund</author><pubDate>Wed, 07 Aug 2024 12:33:21 GMT</pubDate><guid isPermaLink="true">https://numun.fund/what-i-learned-at-the-numun-disinfo-gathering/</guid></item><item><title>My keyboard was misbehaving so I had to exploit my NAS</title><link>https://appsec.space/posts/zimaos-casaos-rce/</link><description>&lt;div class="featured-image"&gt;
                &lt;img src="https://imgs.xkcd.com/comics/im_an_idiot.png" /&gt;
            &lt;/div&gt;&lt;p&gt;I recently received my &lt;a href="https://www.zimaspace.com/products/cube-personal-cloud" rel="noopener noreferrer" target="_blank"&gt;ZimaCube&lt;/a&gt;: a NAS from &lt;a href="https://github.com/IceWhaleTech" rel="noopener noreferrer" target="_blank"&gt;IceWhale&lt;/a&gt;, the same company behind the &lt;a href="https://www.zimaspace.com/products/blade-personal-nas" rel="noopener noreferrer" target="_blank"&gt;ZimaBlade&lt;/a&gt;, &lt;a href="https://www.zimaspace.com/products/single-board-server" rel="noopener noreferrer" target="_blank"&gt;ZimaBoard&lt;/a&gt; and most notably &lt;a href="https://casaos.io/" rel="noopener noreferrer" target="_blank"&gt;CasaOS&lt;/a&gt;, a UI to manage docker applications.&lt;/p&gt;
&lt;p&gt;The ZimaCube ships with the default OS called &lt;code&gt;ZimaOS&lt;/code&gt;.&lt;/p&gt;
&lt;div class="details admonition note open"&gt;
    &lt;div class="details-summary admonition-title"&gt;
        &lt;span class="icon"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"&gt;&lt;/svg&gt;&lt;/span&gt;Note&lt;span class="details-icon"&gt;&lt;svg class="icon" viewBox="0 0 256 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class="details-content"&gt;
        &lt;div class="admonition-content"&gt;&lt;p&gt;This part is a bit tricky but stay with me because I need to clarify this point, otherwise the rest of the article could be hard to understand:&lt;/p&gt;
&lt;p&gt;IceWhale decided to put the word &lt;code&gt;OS&lt;/code&gt; into &lt;code&gt;CasaOS&lt;/code&gt; even if it&amp;rsquo;s not an operating system and then release their NAS, &lt;code&gt;ZimaCube&lt;/code&gt;, with something called &lt;code&gt;ZimaOS&lt;/code&gt; that &lt;em&gt;is&lt;/em&gt; a Linux distro for their NAS that also contains a custom version of &lt;code&gt;CasaOS&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After all, naming things is &lt;a href="https://imgs.xkcd.com/comics/permanence.png" rel="noopener noreferrer" target="_blank"&gt;one of the hardest things&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;Upon booting it up connected to an external monitor I was welcomed with the following TTY message:&lt;/p&gt;
&lt;figure&gt;&lt;img src="/posts/zimaos-casaos-rce/press_alt_f2.jpeg" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;Press alt&amp;#43;F2 to proceed&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Cool, I need to press &lt;code&gt;alt+F2&lt;/code&gt;, there is just one small, insignificant problem&amp;hellip;&lt;/p&gt;
&lt;h2 class="headerLink" id="the-problem"&gt;
    &lt;a class="header-mark" href="#the-problem"&gt;&lt;/a&gt;1 The problem&lt;/h2&gt;&lt;p&gt;All my keyboards are &amp;lt;= 60% so the F row is activated by the &lt;code&gt;Fn&lt;/code&gt; special key, the problem is that for some reasons I&amp;rsquo;m not aware of connecting it to the NAS will not work so no luck.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s analyze the possible alternatives:&lt;/p&gt;
&lt;p&gt;&lt;img alt="mmm" class="tw-inline" height="760" src="/posts/zimaos-casaos-rce/mmm.png" width="1287" /&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Let&amp;rsquo;s exclude &lt;code&gt;B&lt;/code&gt; because is something that no one will do, &lt;a href="https://www.forbes.com/sites/daveywinder/2022/02/05/one-american-hacker-suddenly-takes-down-north-koreas-internet-all-of-it/" rel="noopener noreferrer" target="_blank"&gt;right&lt;/a&gt;? &lt;a href="https://reddit.com/r/IAmA/comments/1divlp3/im_the_hacker_that_brought_down_north_koreas/" rel="noopener noreferrer" target="_blank"&gt;RIGHT&lt;/a&gt;?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;A&lt;/code&gt; seems the easiest one but it will not get delivered until Monday and it&amp;rsquo;s &lt;a href="https://www.msn.com/en-gb/weather/topstories/heatwave-inferno-record-temperatures-sweep-across-italy-and-the-balkans/ar-BB1oDko8" rel="noopener noreferrer" target="_blank"&gt;way too hot outside these days&lt;/a&gt;.
And I also don&amp;rsquo;t want to change a working keyboard.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;D&lt;/code&gt; is kinda reasonable too, but the OS is on an M2 Drive and I have no adapters.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I don&amp;rsquo;t see any other choice of getting my hands dirty and find some good, old unintended solutions.&lt;/p&gt;
&lt;div class="details admonition note"&gt;
    &lt;div class="details-summary admonition-title"&gt;
        &lt;span class="icon"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"&gt;&lt;/svg&gt;&lt;/span&gt;Note&lt;span class="details-icon"&gt;&lt;svg class="icon" viewBox="0 0 256 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class="details-content"&gt;
        &lt;div class="admonition-content"&gt;There were at least 10 different solutions that were faster and cleaner than this one (like patching the OS image file or getting an ESP32 to inject the keystrokes) but it was my first free Saturday in a while and I really love to overcomplicate stuff.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 class="headerLink" id="overcomplicating-the-issue-for-fun-and-boredom"&gt;
    &lt;a class="header-mark" href="#overcomplicating-the-issue-for-fun-and-boredom"&gt;&lt;/a&gt;2 Overcomplicating the issue for fun (and boredom)&lt;/h2&gt;&lt;p&gt;In the same screen where I&amp;rsquo;m asked to press &lt;code&gt;alt+F2&lt;/code&gt; there is a reference to the &lt;em&gt;ZimaOS WebUI&lt;/em&gt; so I decided to start investigating it.&lt;/p&gt;
&lt;figure&gt;&lt;img src="/posts/zimaos-casaos-rce/homepage.png" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;ZimaOS Homepage&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;After loading the homepage I saw something similar to CasaOS interface, but in some way different (as I said, they ship a custom version).&lt;/p&gt;
&lt;p&gt;While ZimaOS was in beta I found a code injection in  the Storage Management API that is still unpatched but unfortunately I need an additional mounted drive to exploit it.&lt;/p&gt;
&lt;p&gt;CasaOS is opensource but any component in ZimaOS isn&amp;rsquo;t, so I have no idea on the exact differences between the two interfaces.&lt;/p&gt;
&lt;div class="details admonition note"&gt;
    &lt;div class="details-summary admonition-title"&gt;
        &lt;span class="icon"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"&gt;&lt;/svg&gt;&lt;/span&gt;TL;DR&lt;span class="details-icon"&gt;&lt;svg class="icon" viewBox="0 0 256 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class="details-content"&gt;
        &lt;div class="admonition-content"&gt;&lt;p&gt;Long story short: the &lt;code&gt;unmount&lt;/code&gt; API is vulnerable to code injection but if no mount point is detected the entrypoint will not be reached.&lt;/p&gt;
&lt;p&gt;More on &lt;a href="https://github.com/IceWhaleTech/CasaOS/security/advisories/GHSA-cjq9-c4gf-jfr2" rel="noopener noreferrer" target="_blank"&gt;the advisory&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The &lt;a href="https://github.com/IceWhaleTech/CasaOS-LocalStorage/blob/ca6d672f152920dca851c24a941136747ccc2ec2/pkg/utils/command/command_helper.go#L62-L69" rel="noopener noreferrer" target="_blank"&gt;code injection&lt;/a&gt; exists because they are concatenating arbitrary user input into a &lt;code&gt;cmd.Exec&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="code-block highlight is-open show-line-numbers  tw-group tw-my-2"&gt;
  &lt;div class="
    
    tw-flex 
    tw-flex-row
    tw-flex-1 
    tw-justify-between 
    tw-w-full tw-bg-bgColor-secondary
    "&gt;      
    &lt;button class="
        code-block-button
        tw-mx-2 
        tw-flex
        tw-flex-row
        tw-flex-1"&gt;
          &lt;div class="group-[.is-open]:tw-rotate-90 tw-transition-[transform] tw-duration-500 tw-ease-in-out print:!tw-hidden tw-w-min tw-h-min tw-my-1 tw-mx-1"&gt;&lt;svg class="icon" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"&gt;&lt;/svg&gt;&lt;/div&gt;
          &lt;p class="tw-select-none !tw-my-1"&gt;go&lt;/p&gt;
      &lt;/button&gt;

   &lt;div class="tw-flex"&gt;
      &lt;button class="
          line-number-button
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.show-line-numbers]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle line numbers"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"&gt;&lt;/svg&gt;&lt;/button&gt;

      &lt;button class="
          wrap-code-button
          tw-select-none 
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.is-wrap]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle code wrap"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"&gt;&lt;/svg&gt;&lt;/button&gt;
      
      &lt;button class="
          copy-code-button
          tw-select-none
          tw-mx-2 
          tw-hidden
          group-[.is-open]:tw-block
          hover:tw-text-fgColor-link 
          print:!tw-hidden" title="Copy code"&gt;
          &lt;span class="copy-icon tw-block"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"&gt;&lt;/svg&gt;&lt;/span&gt;
          &lt;span class="check-icon tw-hidden"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;/button&gt;
        
      &lt;button class="
          tw-select-none 
          tw-mx-2 
          tw-block 
          group-[.is-open]:tw-hidden 
          print:!tw-hidden" disabled="disabled"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"&gt;&lt;/svg&gt;&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;pre class="tw-block tw-m-0 tw-p-0"&gt;&lt;code class="
      chroma 
      !tw-block 
      tw-p-0
      tw-m-0
      tw-transition-[max-height] 
      tw-duration-500 
      tw-ease-in-out 
      group-[.is-closed]:!tw-max-h-0 
      group-[.is-wrap]:tw-text-wrap
      tw-overflow-y-hidden
      tw-overflow-x-auto
      tw-scrollbar-thin
      " id="codeblock-id-1"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ExecLSBLKByPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// the path parameter is sent by the user&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"lsblk"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"-O"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"-J"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"-b"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Before merging the disk (and so calling &lt;code&gt;lsblk&lt;/code&gt;), the &lt;a href="https://github.com/IceWhaleTech/CasaOS-LocalStorage/blob/ca6d672f152920dca851c24a941136747ccc2ec2/service/v2/merge.go#L209-L216" rel="noopener noreferrer" target="_blank"&gt;existence for the mountpoint is checked&lt;/a&gt;:&lt;/p&gt;
&lt;div class="code-block highlight is-open show-line-numbers  tw-group tw-my-2"&gt;
  &lt;div class="
    
    tw-flex 
    tw-flex-row
    tw-flex-1 
    tw-justify-between 
    tw-w-full tw-bg-bgColor-secondary
    "&gt;      
    &lt;button class="
        code-block-button
        tw-mx-2 
        tw-flex
        tw-flex-row
        tw-flex-1"&gt;
          &lt;div class="group-[.is-open]:tw-rotate-90 tw-transition-[transform] tw-duration-500 tw-ease-in-out print:!tw-hidden tw-w-min tw-h-min tw-my-1 tw-mx-1"&gt;&lt;svg class="icon" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"&gt;&lt;/svg&gt;&lt;/div&gt;
          &lt;p class="tw-select-none !tw-my-1"&gt;go&lt;/p&gt;
      &lt;/button&gt;

   &lt;div class="tw-flex"&gt;
      &lt;button class="
          line-number-button
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.show-line-numbers]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle line numbers"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"&gt;&lt;/svg&gt;&lt;/button&gt;

      &lt;button class="
          wrap-code-button
          tw-select-none 
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.is-wrap]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle code wrap"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"&gt;&lt;/svg&gt;&lt;/button&gt;
      
      &lt;button class="
          copy-code-button
          tw-select-none
          tw-mx-2 
          tw-hidden
          group-[.is-open]:tw-block
          hover:tw-text-fgColor-link 
          print:!tw-hidden" title="Copy code"&gt;
          &lt;span class="copy-icon tw-block"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"&gt;&lt;/svg&gt;&lt;/span&gt;
          &lt;span class="check-icon tw-hidden"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;/button&gt;
        
      &lt;button class="
          tw-select-none 
          tw-mx-2 
          tw-block 
          group-[.is-open]:tw-hidden 
          print:!tw-hidden" disabled="disabled"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"&gt;&lt;/svg&gt;&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;pre class="tw-block tw-m-0 tw-p-0"&gt;&lt;code class="
      chroma 
      !tw-block 
      tw-p-0
      tw-m-0
      tw-transition-[max-height] 
      tw-duration-500 
      tw-ease-in-out 
      group-[.is-closed]:!tw-max-h-0 
      group-[.is-wrap]:tw-text-wrap
      tw-overflow-y-hidden
      tw-overflow-x-auto
      tw-scrollbar-thin
      " id="codeblock-id-2"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;excludeVolumesWithWrongMountPointAndUUID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;volumes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;model2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Volume&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;model2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Volume&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;filterVolumes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;model2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Volume&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// This function checks if the mountpoint exists&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// If it returns err the vulnerable call is not reached&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;partition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetDevicePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to corresponding device path by volume UUID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"uuid"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UUID&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// Call the vulerable function&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;par&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ExecLSBLKByPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Unfortunately I have no empty disks at the moment so I have to find another bug.&lt;/p&gt;
&lt;h3 class="headerLink" id="arbitrary-file-read"&gt;
    &lt;a class="header-mark" href="#arbitrary-file-read"&gt;&lt;/a&gt;2.1 Arbitrary file read&lt;/h3&gt;&lt;p&gt;This UI has 3 main features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;App Store&lt;/code&gt; that basically let you download apps that run in Docker via &lt;code&gt;docker-compose&lt;/code&gt; files (more on this later).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;Files&lt;/code&gt; app that allows you to navigate the filesystem (only partially)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;ZVM&lt;/code&gt; app that is a wrapper on &lt;code&gt;libvirt&lt;/code&gt; and allows virtualization.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I started with the &lt;code&gt;Files&lt;/code&gt; app since having a built-in function to read/write would be a nice advantage.&lt;/p&gt;
&lt;figure&gt;&lt;img src="/posts/zimaos-casaos-rce/files_app.png" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;The Files app&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;We are forced to read and write the content of &lt;code&gt;/DATA&lt;/code&gt; and &lt;code&gt;/Media&lt;/code&gt; but looking at the file download function there is a pretty straightforward parameter that will allow to read arbitray files.&lt;/p&gt;
&lt;p&gt;Sending a &lt;code&gt;GET&lt;/code&gt; request to &lt;code&gt;/v3/file&lt;/code&gt; with the paramether &lt;code&gt;files&lt;/code&gt; to any path outside the webroot will do the trick.&lt;/p&gt;
&lt;figure&gt;&lt;img src="/posts/zimaos-casaos-rce/arbitrary_file_read.png" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;Details of the arbitray file read request&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 class="headerLink" id="looking-at-casaos-source-code"&gt;
    &lt;a class="header-mark" href="#looking-at-casaos-source-code"&gt;&lt;/a&gt;2.2 Looking at CasaOS source code&lt;/h3&gt;&lt;p&gt;The API for the &lt;code&gt;Files&lt;/code&gt; app is under &lt;code&gt;/v3/file&lt;/code&gt;, but sometimes also &lt;code&gt;/v2_1/files/file&lt;/code&gt;, we have no source for it but answers and the requests parameters looks very similar to &lt;a href="https://github.com/IceWhaleTech/CasaOS/blob/main/service/file.go" rel="noopener noreferrer" target="_blank"&gt;CasaOS files app&lt;/a&gt; so i supposeed that finding any vulnerability in that app may also work on ZimaOS WebUI.&lt;/p&gt;
&lt;p&gt;Looks like &lt;a href="https://github.com/IceWhaleTech/CasaOS/blob/b331c484f593cfd155a16b03d3f175cecea4c732/service/connections.go#L59-L61" rel="noopener noreferrer" target="_blank"&gt;there was a code injection&lt;/a&gt; in the &lt;code&gt;MountSmaba&lt;/code&gt; (yes, there is a typo) function since &lt;code&gt;host&lt;/code&gt; is appended directly to the command string and &lt;a href="https://github.com/IceWhaleTech/CasaOS/blob/b331c484f593cfd155a16b03d3f175cecea4c732/route/v1/samba.go#L150-L153" rel="noopener noreferrer" target="_blank"&gt;the only validation mechanism&lt;/a&gt; checks &lt;a href="https://github.com/IceWhaleTech/CasaOS/blob/b331c484f593cfd155a16b03d3f175cecea4c732/pkg/utils/ip_helper/ip.go#L10-L15" rel="noopener noreferrer" target="_blank"&gt;the number of &lt;code&gt;:&lt;/code&gt; in the string&lt;/a&gt; but (un)fortunately was patched last year with &lt;a href="https://github.com/IceWhaleTech/CasaOS/pull/1021/files" rel="noopener noreferrer" target="_blank"&gt;this pull request&lt;/a&gt;.&lt;/p&gt;
&lt;h4 class="headerLink" id="bonus-1-executing-arbitrary-commands-during-the-update-process"&gt;
    &lt;a class="header-mark" href="#bonus-1-executing-arbitrary-commands-during-the-update-process"&gt;&lt;/a&gt;2.2.1 Bonus #1: Executing arbitrary commands during the update process&lt;/h4&gt;&lt;p&gt;IceWhale decided to handle system update with &lt;a href="https://github.com/IceWhaleTech/CasaOS/blob/8f7c99779fe31026d2b0d0fe3cb15cb25c0ebb82/service/system.go#L358-L375" rel="noopener noreferrer" target="_blank"&gt;this function&lt;/a&gt;:&lt;/p&gt;
&lt;div class="code-block highlight is-open show-line-numbers  tw-group tw-my-2"&gt;
  &lt;div class="
    
    tw-flex 
    tw-flex-row
    tw-flex-1 
    tw-justify-between 
    tw-w-full tw-bg-bgColor-secondary
    "&gt;      
    &lt;button class="
        code-block-button
        tw-mx-2 
        tw-flex
        tw-flex-row
        tw-flex-1"&gt;
          &lt;div class="group-[.is-open]:tw-rotate-90 tw-transition-[transform] tw-duration-500 tw-ease-in-out print:!tw-hidden tw-w-min tw-h-min tw-my-1 tw-mx-1"&gt;&lt;svg class="icon" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"&gt;&lt;/svg&gt;&lt;/div&gt;
          &lt;p class="tw-select-none !tw-my-1"&gt;go&lt;/p&gt;
      &lt;/button&gt;

   &lt;div class="tw-flex"&gt;
      &lt;button class="
          line-number-button
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.show-line-numbers]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle line numbers"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"&gt;&lt;/svg&gt;&lt;/button&gt;

      &lt;button class="
          wrap-code-button
          tw-select-none 
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.is-wrap]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle code wrap"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"&gt;&lt;/svg&gt;&lt;/button&gt;
      
      &lt;button class="
          copy-code-button
          tw-select-none
          tw-mx-2 
          tw-hidden
          group-[.is-open]:tw-block
          hover:tw-text-fgColor-link 
          print:!tw-hidden" title="Copy code"&gt;
          &lt;span class="copy-icon tw-block"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"&gt;&lt;/svg&gt;&lt;/span&gt;
          &lt;span class="check-icon tw-hidden"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;/button&gt;
        
      &lt;button class="
          tw-select-none 
          tw-mx-2 
          tw-block 
          group-[.is-open]:tw-hidden 
          print:!tw-hidden" disabled="disabled"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"&gt;&lt;/svg&gt;&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;pre class="tw-block tw-m-0 tw-p-0"&gt;&lt;code class="
      chroma 
      !tw-block 
      tw-p-0
      tw-m-0
      tw-transition-[max-height] 
      tw-duration-500 
      tw-ease-in-out 
      group-[.is-closed]:!tw-max-h-0 
      group-[.is-wrap]:tw-text-wrap
      tw-overflow-y-hidden
      tw-overflow-x-auto
      tw-scrollbar-thin
      " id="codeblock-id-3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;systemService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;UpdateSystemVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;keyName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"casa_version"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;Cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;keyName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AppInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/upgrade.log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AppInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/upgrade.log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AppInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/upgrade.log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ServerInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UpdateUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnlyExec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -fsSL "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ServerInfo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;UpdateUrl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;" | bash"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;osRelease&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadOSRelease&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;go&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;command2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OnlyExec&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"curl -fsSL https://get.casaos.io/update?t="&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;osRelease&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"MANUFACTURER"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;" | bash"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;the TL;DR is: a script get downloaded from &lt;code&gt;https://get.casaos.io/update&lt;/code&gt; and piped to bash.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s honestly a questionable design but I&amp;rsquo;m not here to judge (not today atleast), but since they already got native code runnig the update script could be a native function instead.
And will for sure cut out issues like the domain being unreachable or, even worse, the update script compromised.&lt;/p&gt;
&lt;div class="details admonition note"&gt;
    &lt;div class="details-summary admonition-title"&gt;
        &lt;span class="icon"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"&gt;&lt;/svg&gt;&lt;/span&gt;Note&lt;span class="details-icon"&gt;&lt;svg class="icon" viewBox="0 0 256 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class="details-content"&gt;
        &lt;div class="admonition-content"&gt;The source of the update script is available &lt;a href="https://github.com/IceWhaleTech/get" rel="noopener noreferrer" target="_blank"&gt;here&lt;/a&gt;.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;During the update process, there are two paths that could be used during a race condition to turn an arbitrary file write into a code execution since every script in this directory get executed:&lt;/p&gt;
&lt;p&gt;One is: &lt;code&gt;/tmp/casaos-installer/${randomstring}/build/scripts/migration/script.d&lt;/code&gt;:&lt;/p&gt;
&lt;div class="code-block highlight is-open show-line-numbers  tw-group tw-my-2"&gt;
  &lt;div class="
    
    tw-flex 
    tw-flex-row
    tw-flex-1 
    tw-justify-between 
    tw-w-full tw-bg-bgColor-secondary
    "&gt;      
    &lt;button class="
        code-block-button
        tw-mx-2 
        tw-flex
        tw-flex-row
        tw-flex-1"&gt;
          &lt;div class="group-[.is-open]:tw-rotate-90 tw-transition-[transform] tw-duration-500 tw-ease-in-out print:!tw-hidden tw-w-min tw-h-min tw-my-1 tw-mx-1"&gt;&lt;svg class="icon" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"&gt;&lt;/svg&gt;&lt;/div&gt;
          &lt;p class="tw-select-none !tw-my-1"&gt;bash&lt;/p&gt;
      &lt;/button&gt;

   &lt;div class="tw-flex"&gt;
      &lt;button class="
          line-number-button
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.show-line-numbers]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle line numbers"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"&gt;&lt;/svg&gt;&lt;/button&gt;

      &lt;button class="
          wrap-code-button
          tw-select-none 
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.is-wrap]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle code wrap"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"&gt;&lt;/svg&gt;&lt;/button&gt;
      
      &lt;button class="
          copy-code-button
          tw-select-none
          tw-mx-2 
          tw-hidden
          group-[.is-open]:tw-block
          hover:tw-text-fgColor-link 
          print:!tw-hidden" title="Copy code"&gt;
          &lt;span class="copy-icon tw-block"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"&gt;&lt;/svg&gt;&lt;/span&gt;
          &lt;span class="check-icon tw-hidden"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;/button&gt;
        
      &lt;button class="
          tw-select-none 
          tw-mx-2 
          tw-block 
          group-[.is-open]:tw-hidden 
          print:!tw-hidden" disabled="disabled"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"&gt;&lt;/svg&gt;&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;pre class="tw-block tw-m-0 tw-p-0"&gt;&lt;code class="
      chroma 
      !tw-block 
      tw-p-0
      tw-m-0
      tw-transition-[max-height] 
      tw-duration-500 
      tw-ease-in-out 
      group-[.is-closed]:!tw-max-h-0 
      group-[.is-wrap]:tw-text-wrap
      tw-overflow-y-hidden
      tw-overflow-x-auto
      tw-scrollbar-thin
      " id="codeblock-id-4"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DownloadAndInstallCasaOS&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -z &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;# TMP_ROOT is TMP_ROOT=/tmp/casaos-installer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nv"&gt;TMP_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sudo_cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; mktemp -d -p &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMP_ROOT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; Show &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;"Failed to create temporary directory"&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;realpath -e &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TMP_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/build &lt;span class="o"&gt;||&lt;/span&gt; Show &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;"Failed to find build directory"&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="nv"&gt;MIGRATION_SCRIPT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;realpath -e &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/scripts/migration/script.d &lt;span class="o"&gt;||&lt;/span&gt; Show &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;"Failed to find migration script directory"&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# MIGRATION_SCRIPT_DIR is /tmp/casaos-installer/${randomstring}/build/scripts/migration/script.d&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;for&lt;/span&gt; MIGRATION_SCRIPT in &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIGRATION_SCRIPT_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/*.sh&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="c1"&gt;# execute every script in that directory as root&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sudo_cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; bash &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;MIGRATION_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; Show &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;"Failed to run migration script"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;and the other is &lt;code&gt;/tmp/casaos-installer/${randomstring}/build/scripts/setup/script.d&lt;/code&gt;, few lines later in the same function:&lt;/p&gt;
&lt;div class="code-block highlight is-open show-line-numbers  tw-group tw-my-2"&gt;
  &lt;div class="
    
    tw-flex 
    tw-flex-row
    tw-flex-1 
    tw-justify-between 
    tw-w-full tw-bg-bgColor-secondary
    "&gt;      
    &lt;button class="
        code-block-button
        tw-mx-2 
        tw-flex
        tw-flex-row
        tw-flex-1"&gt;
          &lt;div class="group-[.is-open]:tw-rotate-90 tw-transition-[transform] tw-duration-500 tw-ease-in-out print:!tw-hidden tw-w-min tw-h-min tw-my-1 tw-mx-1"&gt;&lt;svg class="icon" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"&gt;&lt;/svg&gt;&lt;/div&gt;
          &lt;p class="tw-select-none !tw-my-1"&gt;bash&lt;/p&gt;
      &lt;/button&gt;

   &lt;div class="tw-flex"&gt;
      &lt;button class="
          line-number-button
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.show-line-numbers]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle line numbers"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"&gt;&lt;/svg&gt;&lt;/button&gt;

      &lt;button class="
          wrap-code-button
          tw-select-none 
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.is-wrap]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle code wrap"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"&gt;&lt;/svg&gt;&lt;/button&gt;
      
      &lt;button class="
          copy-code-button
          tw-select-none
          tw-mx-2 
          tw-hidden
          group-[.is-open]:tw-block
          hover:tw-text-fgColor-link 
          print:!tw-hidden" title="Copy code"&gt;
          &lt;span class="copy-icon tw-block"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"&gt;&lt;/svg&gt;&lt;/span&gt;
          &lt;span class="check-icon tw-hidden"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;/button&gt;
        
      &lt;button class="
          tw-select-none 
          tw-mx-2 
          tw-block 
          group-[.is-open]:tw-hidden 
          print:!tw-hidden" disabled="disabled"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"&gt;&lt;/svg&gt;&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;pre class="tw-block tw-m-0 tw-p-0"&gt;&lt;code class="
      chroma 
      !tw-block 
      tw-p-0
      tw-m-0
      tw-transition-[max-height] 
      tw-duration-500 
      tw-ease-in-out 
      group-[.is-closed]:!tw-max-h-0 
      group-[.is-wrap]:tw-text-wrap
      tw-overflow-y-hidden
      tw-overflow-x-auto
      tw-scrollbar-thin
      " id="codeblock-id-5"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nv"&gt;SETUP_SCRIPT_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;realpath -e &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BUILD_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/scripts/setup/script.d &lt;span class="o"&gt;||&lt;/span&gt; Show &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;"Failed to find setup script directory"&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;for&lt;/span&gt; SETUP_SCRIPT in &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SETUP_SCRIPT_DIR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/*.sh&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="c1"&gt;# again, executing everything in that directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;sudo_cmd&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; bash &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;SETUP_SCRIPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; Show &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="s2"&gt;"Failed to run setup script"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="k"&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;h4 class="headerLink" id="directory-listing"&gt;
    &lt;a class="header-mark" href="#directory-listing"&gt;&lt;/a&gt;2.2.2 Directory listing&lt;/h4&gt;&lt;p&gt;Using &lt;code&gt;mktemp&lt;/code&gt; would be a good enough method to prevent path guessing, if only there wasn&amp;rsquo;t a directory listing in &lt;code&gt;/v2_1/files/file&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Indeed, sending a &lt;code&gt;GET&lt;/code&gt; request to &lt;code&gt;/v2_1/files/file&lt;/code&gt; with the parameter &lt;code&gt;path&lt;/code&gt; pointing to an arbitrary path will result in a directory listing, even if we shouldn&amp;rsquo;t be able to interact with anthing outside &lt;code&gt;/Data&lt;/code&gt; and &lt;code&gt;/Media&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src="/posts/zimaos-casaos-rce/directory_listing.png" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;Directory Listing&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;This way we can detect if any directory has been created in &lt;code&gt;/tmp/casaos-installer/&lt;/code&gt; and find the name of the temporary directory.&lt;/p&gt;
&lt;h4 class="headerLink" id="path-traversal-in-upload"&gt;
    &lt;a class="header-mark" href="#path-traversal-in-upload"&gt;&lt;/a&gt;2.2.3 Path traversal in upload&lt;/h4&gt;&lt;p&gt;We have an arbitrary file read, a directory listing and two entrypoints (even if we need to perform a system update), we just need an arbitrary file write to complete the chain.&lt;/p&gt;
&lt;p&gt;Luckily (only for us tho), the same endpoint we used to read arbitrary files &lt;a href="https://github.com/IceWhaleTech/CasaOS/blob/8f7c99779fe31026d2b0d0fe3cb15cb25c0ebb82/service/file_upload.go#L60-L167" rel="noopener noreferrer" target="_blank"&gt;has an upload function with a path traversal vulnerability &lt;/a&gt;&lt;/p&gt;
&lt;div class="code-block highlight is-open show-line-numbers  tw-group tw-my-2"&gt;
  &lt;div class="
    
    tw-flex 
    tw-flex-row
    tw-flex-1 
    tw-justify-between 
    tw-w-full tw-bg-bgColor-secondary
    "&gt;      
    &lt;button class="
        code-block-button
        tw-mx-2 
        tw-flex
        tw-flex-row
        tw-flex-1"&gt;
          &lt;div class="group-[.is-open]:tw-rotate-90 tw-transition-[transform] tw-duration-500 tw-ease-in-out print:!tw-hidden tw-w-min tw-h-min tw-my-1 tw-mx-1"&gt;&lt;svg class="icon" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"&gt;&lt;/svg&gt;&lt;/div&gt;
          &lt;p class="tw-select-none !tw-my-1"&gt;go&lt;/p&gt;
      &lt;/button&gt;

   &lt;div class="tw-flex"&gt;
      &lt;button class="
          line-number-button
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.show-line-numbers]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle line numbers"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"&gt;&lt;/svg&gt;&lt;/button&gt;

      &lt;button class="
          wrap-code-button
          tw-select-none 
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.is-wrap]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle code wrap"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"&gt;&lt;/svg&gt;&lt;/button&gt;
      
      &lt;button class="
          copy-code-button
          tw-select-none
          tw-mx-2 
          tw-hidden
          group-[.is-open]:tw-block
          hover:tw-text-fgColor-link 
          print:!tw-hidden" title="Copy code"&gt;
          &lt;span class="copy-icon tw-block"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"&gt;&lt;/svg&gt;&lt;/span&gt;
          &lt;span class="check-icon tw-hidden"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;/button&gt;
        
      &lt;button class="
          tw-select-none 
          tw-mx-2 
          tw-block 
          group-[.is-open]:tw-hidden 
          print:!tw-hidden" disabled="disabled"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"&gt;&lt;/svg&gt;&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;pre class="tw-block tw-m-0 tw-p-0"&gt;&lt;code class="
      chroma 
      !tw-block 
      tw-p-0
      tw-m-0
      tw-transition-[max-height] 
      tw-duration-500 
      tw-ease-in-out 
      group-[.is-closed]:!tw-max-h-0 
      group-[.is-wrap]:tw-text-wrap
      tw-overflow-y-hidden
      tw-overflow-x-auto
      tw-scrollbar-thin
      " id="codeblock-id-6"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;FileUploadService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;UploadFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// path and relativePath are sent by the user&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;relativePath&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;".tmp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_WRONLY&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;O_CREATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mo"&gt;0644&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;bin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;defer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;io&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;figure&gt;&lt;img src="/posts/zimaos-casaos-rce/arbitrary_file_upload.png" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;Arbitrary file upload&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 class="headerLink" id="a-valid-entrypoint"&gt;
    &lt;a class="header-mark" href="#a-valid-entrypoint"&gt;&lt;/a&gt;2.2.4 A valid entrypoint&lt;/h4&gt;&lt;p&gt;Digging deeper I found &lt;a href="https://github.com/IceWhaleTech/CasaOS/blob/8f7c99779fe31026d2b0d0fe3cb15cb25c0ebb82/pkg/utils/command/command_helper.go#L111" rel="noopener noreferrer" target="_blank"&gt;another interesting entrypoint to turn arbitrary file write into code execution&lt;/a&gt;:
This time the only requirement is a reboot (the webUI allows to reboot from web interface).&lt;/p&gt;
&lt;p&gt;In brief, every file placed in &lt;code&gt;/etc/casaos/start.d&lt;/code&gt; will be executed &lt;a href="https://github.com/IceWhaleTech/CasaOS/blob/8f7c99779fe31026d2b0d0fe3cb15cb25c0ebb82/main.go#L203" rel="noopener noreferrer" target="_blank"&gt;at boot&lt;/a&gt;:&lt;/p&gt;
&lt;div class="code-block highlight is-open show-line-numbers  tw-group tw-my-2"&gt;
  &lt;div class="
    
    tw-flex 
    tw-flex-row
    tw-flex-1 
    tw-justify-between 
    tw-w-full tw-bg-bgColor-secondary
    "&gt;      
    &lt;button class="
        code-block-button
        tw-mx-2 
        tw-flex
        tw-flex-row
        tw-flex-1"&gt;
          &lt;div class="group-[.is-open]:tw-rotate-90 tw-transition-[transform] tw-duration-500 tw-ease-in-out print:!tw-hidden tw-w-min tw-h-min tw-my-1 tw-mx-1"&gt;&lt;svg class="icon" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"&gt;&lt;/svg&gt;&lt;/div&gt;
          &lt;p class="tw-select-none !tw-my-1"&gt;go&lt;/p&gt;
      &lt;/button&gt;

   &lt;div class="tw-flex"&gt;
      &lt;button class="
          line-number-button
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.show-line-numbers]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle line numbers"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"&gt;&lt;/svg&gt;&lt;/button&gt;

      &lt;button class="
          wrap-code-button
          tw-select-none 
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.is-wrap]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle code wrap"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"&gt;&lt;/svg&gt;&lt;/button&gt;
      
      &lt;button class="
          copy-code-button
          tw-select-none
          tw-mx-2 
          tw-hidden
          group-[.is-open]:tw-block
          hover:tw-text-fgColor-link 
          print:!tw-hidden" title="Copy code"&gt;
          &lt;span class="copy-icon tw-block"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"&gt;&lt;/svg&gt;&lt;/span&gt;
          &lt;span class="check-icon tw-hidden"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;/button&gt;
        
      &lt;button class="
          tw-select-none 
          tw-mx-2 
          tw-block 
          group-[.is-open]:tw-hidden 
          print:!tw-hidden" disabled="disabled"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"&gt;&lt;/svg&gt;&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;pre class="tw-block tw-m-0 tw-p-0"&gt;&lt;code class="
      chroma 
      !tw-block 
      tw-p-0
      tw-m-0
      tw-transition-[max-height] 
      tw-duration-500 
      tw-ease-in-out 
      group-[.is-closed]:!tw-max-h-0 
      group-[.is-wrap]:tw-text-wrap
      tw-overflow-y-hidden
      tw-overflow-x-auto
      tw-scrollbar-thin
      " id="codeblock-id-7"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ExecuteScripts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scriptDirectory&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// scriptDirectory is /etc/casaos/start.d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ReadDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scriptDirectory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// for each file in /etc/casaos/start.d&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;	&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;scriptFilepath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;filepath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scriptDirectory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="c1"&gt;// execute the script&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;		&lt;/span&gt;&lt;span class="nx"&gt;cmd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interpreter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;scriptFilepath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;figure&gt;&lt;img src="/posts/zimaos-casaos-rce/ExecuteScripts_logic.png" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;ExecuteScripts() logic flow&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h4 class="headerLink" id="abusing-the-move-and-copy-funtionality"&gt;
    &lt;a class="header-mark" href="#abusing-the-move-and-copy-funtionality"&gt;&lt;/a&gt;2.2.5 Abusing the move and copy funtionality&lt;/h4&gt;&lt;p&gt;Let&amp;rsquo;s pretend for a second we don&amp;rsquo;t have any of the previous arbitrary read/write vulnerabilities, what else can we use?&lt;/p&gt;
&lt;p&gt;Another intereting functionality CasaOS has is the ability to &lt;a href="https://github.com/IceWhaleTech/CasaOS/blob/8f7c99779fe31026d2b0d0fe3cb15cb25c0ebb82/service/file.go#L119-L126" rel="noopener noreferrer" target="_blank"&gt;move and copy files&lt;/a&gt; (supposedly inside the allowed directory).&lt;/p&gt;
&lt;p&gt;Since there is no mechanism in place to check &lt;em&gt;what&lt;/em&gt; and &lt;em&gt;where&lt;/em&gt; is being moved/copied, we can abuse this functionality to get read/write in the whole filesystem.&lt;/p&gt;
&lt;div class="code-block highlight is-open show-line-numbers  tw-group tw-my-2"&gt;
  &lt;div class="
    
    tw-flex 
    tw-flex-row
    tw-flex-1 
    tw-justify-between 
    tw-w-full tw-bg-bgColor-secondary
    "&gt;      
    &lt;button class="
        code-block-button
        tw-mx-2 
        tw-flex
        tw-flex-row
        tw-flex-1"&gt;
          &lt;div class="group-[.is-open]:tw-rotate-90 tw-transition-[transform] tw-duration-500 tw-ease-in-out print:!tw-hidden tw-w-min tw-h-min tw-my-1 tw-mx-1"&gt;&lt;svg class="icon" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"&gt;&lt;/svg&gt;&lt;/div&gt;
          &lt;p class="tw-select-none !tw-my-1"&gt;go&lt;/p&gt;
      &lt;/button&gt;

   &lt;div class="tw-flex"&gt;
      &lt;button class="
          line-number-button
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.show-line-numbers]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle line numbers"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"&gt;&lt;/svg&gt;&lt;/button&gt;

      &lt;button class="
          wrap-code-button
          tw-select-none 
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.is-wrap]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle code wrap"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"&gt;&lt;/svg&gt;&lt;/button&gt;
      
      &lt;button class="
          copy-code-button
          tw-select-none
          tw-mx-2 
          tw-hidden
          group-[.is-open]:tw-block
          hover:tw-text-fgColor-link 
          print:!tw-hidden" title="Copy code"&gt;
          &lt;span class="copy-icon tw-block"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"&gt;&lt;/svg&gt;&lt;/span&gt;
          &lt;span class="check-icon tw-hidden"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;/button&gt;
        
      &lt;button class="
          tw-select-none 
          tw-mx-2 
          tw-block 
          group-[.is-open]:tw-hidden 
          print:!tw-hidden" disabled="disabled"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"&gt;&lt;/svg&gt;&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;pre class="tw-block tw-m-0 tw-p-0"&gt;&lt;code class="
      chroma 
      !tw-block 
      tw-p-0
      tw-m-0
      tw-transition-[max-height] 
      tw-duration-500 
      tw-ease-in-out 
      group-[.is-closed]:!tw-max-h-0 
      group-[.is-wrap]:tw-text-wrap
      tw-overflow-y-hidden
      tw-overflow-x-auto
      tw-scrollbar-thin
      " id="codeblock-id-8"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"move"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;lastPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;From&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LastIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;From&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CheckNotExist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lastPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Style&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"skip"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;Finished&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;lastPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CopyDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;From&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;To&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Style&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RemoveAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;From&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"file move error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"err"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;MoveFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;From&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;To&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;"/"&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;lastPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;!=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"MoveFile error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;zap&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"err"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;						&lt;/span&gt;&lt;span class="k"&gt;continue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;					&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;				&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;			&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;Indeed, sending a &lt;code&gt;POST&lt;/code&gt; request to &lt;code&gt;/v2_1/files/task&lt;/code&gt; with the following body:&lt;/p&gt;
&lt;div class="code-block highlight is-open show-line-numbers  tw-group tw-my-2"&gt;
  &lt;div class="
    
    tw-flex 
    tw-flex-row
    tw-flex-1 
    tw-justify-between 
    tw-w-full tw-bg-bgColor-secondary
    "&gt;      
    &lt;button class="
        code-block-button
        tw-mx-2 
        tw-flex
        tw-flex-row
        tw-flex-1"&gt;
          &lt;div class="group-[.is-open]:tw-rotate-90 tw-transition-[transform] tw-duration-500 tw-ease-in-out print:!tw-hidden tw-w-min tw-h-min tw-my-1 tw-mx-1"&gt;&lt;svg class="icon" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"&gt;&lt;/svg&gt;&lt;/div&gt;
          &lt;p class="tw-select-none !tw-my-1"&gt;json&lt;/p&gt;
      &lt;/button&gt;

   &lt;div class="tw-flex"&gt;
      &lt;button class="
          line-number-button
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.show-line-numbers]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle line numbers"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"&gt;&lt;/svg&gt;&lt;/button&gt;

      &lt;button class="
          wrap-code-button
          tw-select-none 
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.is-wrap]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle code wrap"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M16 132h416c8.837 0 16-7.163 16-16V76c0-8.837-7.163-16-16-16H16C7.163 60 0 67.163 0 76v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16zm0 160h416c8.837 0 16-7.163 16-16v-40c0-8.837-7.163-16-16-16H16c-8.837 0-16 7.163-16 16v40c0 8.837 7.163 16 16 16z"&gt;&lt;/svg&gt;&lt;/button&gt;
      
      &lt;button class="
          copy-code-button
          tw-select-none
          tw-mx-2 
          tw-hidden
          group-[.is-open]:tw-block
          hover:tw-text-fgColor-link 
          print:!tw-hidden" title="Copy code"&gt;
          &lt;span class="copy-icon tw-block"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M433.941 65.941l-51.882-51.882A48 48 0 0 0 348.118 0H176c-26.51 0-48 21.49-48 48v48H48c-26.51 0-48 21.49-48 48v320c0 26.51 21.49 48 48 48h224c26.51 0 48-21.49 48-48v-48h80c26.51 0 48-21.49 48-48V99.882a48 48 0 0 0-14.059-33.941zM266 464H54a6 6 0 0 1-6-6V150a6 6 0 0 1 6-6h74v224c0 26.51 21.49 48 48 48h96v42a6 6 0 0 1-6 6zm128-96H182a6 6 0 0 1-6-6V54a6 6 0 0 1 6-6h106v88c0 13.255 10.745 24 24 24h88v202a6 6 0 0 1-6 6zm6-256h-64V48h9.632c1.591 0 3.117.632 4.243 1.757l48.368 48.368a6 6 0 0 1 1.757 4.243V112z"&gt;&lt;/svg&gt;&lt;/span&gt;
          &lt;span class="check-icon tw-hidden"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"&gt;&lt;/svg&gt;&lt;/span&gt;
      &lt;/button&gt;
        
      &lt;button class="
          tw-select-none 
          tw-mx-2 
          tw-block 
          group-[.is-open]:tw-hidden 
          print:!tw-hidden" disabled="disabled"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M328 256c0 39.8-32.2 72-72 72s-72-32.2-72-72 32.2-72 72-72 72 32.2 72 72zm104-72c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72zm-352 0c-39.8 0-72 32.2-72 72s32.2 72 72 72 72-32.2 72-72-32.2-72-72-72z"&gt;&lt;/svg&gt;&lt;/button&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;pre class="tw-block tw-m-0 tw-p-0"&gt;&lt;code class="
      chroma 
      !tw-block 
      tw-p-0
      tw-m-0
      tw-transition-[max-height] 
      tw-duration-500 
      tw-ease-in-out 
      group-[.is-closed]:!tw-max-h-0 
      group-[.is-wrap]:tw-text-wrap
      tw-overflow-y-hidden
      tw-overflow-x-auto
      tw-scrollbar-thin
      " id="codeblock-id-9"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nt"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"/etc/casaos/start.d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nt"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"move"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="nt"&gt;"item"&lt;/span&gt;&lt;span class="p"&gt;:[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;		&lt;span class="nt"&gt;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"/media/ZimaOS-HD/Downloads/payload"&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;	 &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;allows us to move files around in places we are not supposed to, easy as that.&lt;/p&gt;
&lt;p&gt;A standard WebUI user could just move files in the &lt;code&gt;/Media&lt;/code&gt; directory, that is allowed to read, or upload files to &lt;code&gt;/Media&lt;/code&gt; and then move them around arbitrarily.
This functionality can be effectively abused to get full code execution on the machine since we eariler found that everything in &lt;code&gt;/etc/casaos/start.d&lt;/code&gt; grants execution at boot.&lt;/p&gt;
&lt;h3 class="headerLink" id="finalizing-the-exploit"&gt;
    &lt;a class="header-mark" href="#finalizing-the-exploit"&gt;&lt;/a&gt;2.3 Finalizing the exploit&lt;/h3&gt;&lt;p&gt;I finally have enough to resolve the issue.&lt;/p&gt;
&lt;p&gt;I wrote few lines of code to do the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Write a file containing a reverse shell to &lt;code&gt;/media/ZimaOS-HD/Downloads/&lt;/code&gt; (intended functionality)&lt;/li&gt;
&lt;li&gt;Move the file to &lt;code&gt;/etc/casaos/start.d&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Use the directory listing to check if the file has been succesfully moved&lt;/li&gt;
&lt;li&gt;Reboot the machine&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="details admonition note"&gt;
    &lt;div class="details-summary admonition-title"&gt;
        &lt;span class="icon"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"&gt;&lt;/svg&gt;&lt;/span&gt;Note&lt;span class="details-icon"&gt;&lt;svg class="icon" viewBox="0 0 256 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class="details-content"&gt;
        &lt;div class="admonition-content"&gt;Since the script is executed at boot the network interface could still be in the process of going up, so remember to add a small timeout before executing the payload.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;The PoC is available at &lt;a href="https://github.com/himazawa/zimaos-postauth-rce" rel="noopener noreferrer" target="_blank"&gt;https://github.com/himazawa/zimaos-postauth-rce&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img src="/posts/zimaos-casaos-rce/rev_shell.png" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;Problem Solved&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;div class="details admonition note open"&gt;
    &lt;div class="details-summary admonition-title"&gt;
        &lt;span class="icon"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M497.9 142.1l-46.1 46.1c-4.7 4.7-12.3 4.7-17 0l-111-111c-4.7-4.7-4.7-12.3 0-17l46.1-46.1c18.7-18.7 49.1-18.7 67.9 0l60.1 60.1c18.8 18.7 18.8 49.1 0 67.9zM284.2 99.8L21.6 362.4.4 483.9c-2.9 16.4 11.4 30.6 27.8 27.8l121.5-21.3 262.6-262.6c4.7-4.7 4.7-12.3 0-17l-111-111c-4.8-4.7-12.4-4.7-17.1 0zM124.1 339.9c-5.5-5.5-5.5-14.3 0-19.8l154-154c5.5-5.5 14.3-5.5 19.8 0s5.5 14.3 0 19.8l-154 154c-5.5 5.5-14.3 5.5-19.8 0zM88 424h48v36.3l-64.5 11.3-31.1-31.1L51.7 376H88v48z"&gt;&lt;/svg&gt;&lt;/span&gt;Note&lt;span class="details-icon"&gt;&lt;svg class="icon" viewBox="0 0 256 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class="details-content"&gt;
        &lt;div class="admonition-content"&gt;Due to the number of vulnerabilities found there are plenty of other ways to do the same thing. I decided to use the move functionality just because it looked more interesting to me.
Feel free to experiment and write your own chain.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h3 class="headerLink" id="bonus-2-dom-based-xss"&gt;
    &lt;a class="header-mark" href="#bonus-2-dom-based-xss"&gt;&lt;/a&gt;2.4 Bonus #2: DOM based XSS&lt;/h3&gt;&lt;p&gt;I have no interest in making the chain remote, but there is a DOM based xss at &lt;code&gt;/v2/app_management/web/sync/appstore&lt;/code&gt; in the &lt;code&gt;url&lt;/code&gt; parameter and the payload is &lt;code&gt;:&amp;lt;img src=x onerror=alert(1) &amp;gt;&lt;/code&gt;.
Note the &lt;code&gt;:&lt;/code&gt; because they are needed in order to make this &lt;a href="https://github.com/IceWhaleTech/CasaOS-AppManagement/blob/8d0082ad64c2fc4209031284abcf7c9d252e0d72/service/appstore_management.go#L99" rel="noopener noreferrer" target="_blank"&gt;function fail&lt;/a&gt;&lt;/p&gt;
&lt;figure&gt;&lt;img src="/posts/zimaos-casaos-rce/XSS.png" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;XSS&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 class="headerLink" id="conclusion"&gt;
    &lt;a class="header-mark" href="#conclusion"&gt;&lt;/a&gt;3 Conclusion&lt;/h2&gt;&lt;p&gt;In the end it was a fun Saturday that demostrated that good old web vulnerabilities are still a thing :P
CasaOS and ZimaOS are being reworked right now to fix the issues and the advisories will be published soon.&lt;/p&gt;
&lt;h2 class="headerLink" id="timeline"&gt;
    &lt;a class="header-mark" href="#timeline"&gt;&lt;/a&gt;4 Timeline&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;24/06/2024 - Vulnerability Reported&lt;/li&gt;
&lt;li&gt;03/08/2024 - A fix for the code injection was released&lt;/li&gt;
&lt;li&gt;07/08/2024 - Asked IceWhale permission to release this post&lt;/li&gt;
&lt;/ul&gt;</description><author>appsec &amp;amp; stuff</author><pubDate>Wed, 07 Aug 2024 12:00:24 GMT</pubDate><guid isPermaLink="true">https://appsec.space/posts/zimaos-casaos-rce/</guid></item><item><title>State of the fasterthanlime 2024</title><link>https://fasterthanli.me/articles/state-of-the-fasterthanlime-2024</link><description>&lt;p&gt;It’s time for some personal &lt;em&gt;and&lt;/em&gt; professional news!&lt;/p&gt;

&lt;p&gt;TL;DR: I started &lt;a href="https://sdr-podcast.com"&gt;a podcast&lt;/a&gt; with &lt;a href="https://jamesmunns.com/contact/"&gt;James&lt;/a&gt;,
I’m stable on antidepressants, I’m giving a &lt;a href="https://www.p99conf.io/"&gt;P99 CONF&lt;/a&gt; about my &lt;a href="https://github.com/bearcove/fluke"&gt;Rust&amp;#x2f;io_uring&amp;#x2f;HTTP work&lt;/a&gt;,
I’m trying on “they&amp;#x2f;them” as pronouns, I’m open-sourcing &lt;a href="https://github.com/bearcove/merde_json"&gt;merde_json&lt;/a&gt;,
&lt;a href="https://github.com/bearcove/rubicon"&gt;rubicon&lt;/a&gt; and others, I got a divorce in 2023, I found a new business model.&lt;/p&gt;

&lt;p&gt;Now that we’re on the same page: let’s unpack this a bit!&lt;/p&gt;

&lt;a class="anchor" href="#a-podcast-with-james" id="a-podcast-with-james"&gt;&lt;/a&gt;


















































&lt;a class="anchor" href="#all-the-personal-news" id="all-the-personal-news"&gt;&lt;/a&gt;




































&lt;a class="anchor" href="#we-re-so-back" id="we-re-so-back"&gt;&lt;/a&gt;

























































































&lt;a class="anchor" href="#a-new-business-model" id="a-new-business-model"&gt;&lt;/a&gt;


























&lt;a class="anchor" href="#corporate-sponsorships" id="corporate-sponsorships"&gt;&lt;/a&gt;










&lt;a class="anchor" href="#that-s-it" id="that-s-it"&gt;&lt;/a&gt;</description><author>fasterthanli.me</author><pubDate>Wed, 07 Aug 2024 10:14:05 GMT</pubDate><guid isPermaLink="true">https://fasterthanli.me/articles/state-of-the-fasterthanlime-2024</guid></item><item><title>Ricotta Cheese is a PsyOp</title><link>https://lagomor.ph/2024/08/ricotta-cheese-is-a-psyop/</link><description>&lt;p&gt;I categorically refuse to believe anybody actually enjoys ricotta cheese. For those unfamiliar, this Italian cheese comes out of a mistaken way to boil milk in the Bronze age, and for some reason hundreds of years later, it&amp;rsquo;s still an ingredient people try to eat.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s tasteless, so all you really have to go off of is its texture, which is chewy, grainy, and offputting - it has the same consistency of curdled milk (because it is), except unlike curdled milk we&amp;rsquo;ve somehow been convinced we&amp;rsquo;re supposed to eat it.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Wed, 07 Aug 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/2024/08/ricotta-cheese-is-a-psyop/</guid></item><item><title>Timewaste Tracker for Reddit &amp;amp; Youtube</title><link>https://lagomor.ph/projects/waste-timer/</link><description>&lt;p&gt;It is incredibly easy to waste time. It is particularly sinister that certain corporations are dedicated to distracting me and wasting my time, when it is in fact a limited resource.&lt;/p&gt;
&lt;p&gt;I do not need to be spending on Reddit or Youtube, being sucked down random rabbit holes of wholy useless information.&lt;/p&gt;
&lt;p&gt;However, blocking them outright is not effective, as often useful information is gated on Reddit, so I&amp;rsquo;ll disable my blocker then get distracted.&lt;/p&gt;</description><author>Home on Lagomorph</author><pubDate>Wed, 07 Aug 2024 09:00:00 GMT</pubDate><guid isPermaLink="true">https://lagomor.ph/projects/waste-timer/</guid></item><item><title>Running my ThinkPad T430 with an eGPU in 2024</title><link>https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/</link><description>&lt;img src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/cover.jpg" /&gt;
          
        
        
        &lt;p&gt;I probably shouldn&amp;rsquo;t have written down &lt;a href="https://ounapuu.ee/posts/2024/07/03/thinkpad-t430-egpu/"&gt;my notes on the eGPU setup I had years ago.&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I’d be lying if I wasn’t considering remaking this setup with everything I’ve learned 6 years later.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oops.&lt;/p&gt;
&lt;p&gt;I got access to an allegedly-faulty AMD Radeon RX 480 and an NVIDIA GTX 1650 for free thanks to my friend, so I ordered
the EXP GDC Beast v8.5c, two sorts of 6/8pin power cables, and a 12V 12A DC power supply.&lt;/p&gt;
&lt;p&gt;The idea was to take a ThinkPad T430, bolt a GPU to it, and use it as a stationary workstation until the laptop
dies or gets too slow for modern tasks. The resale value of the T430 is too low for me to justify selling it, and if there&amp;rsquo;s anyone
out there who can run a T430 into the ground 5+ years from now, then it&amp;rsquo;s probably going to be me.&lt;/p&gt;
&lt;p&gt;Quick specs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;CPU: Intel i7-3820QM&lt;/li&gt;
&lt;li&gt;RAM: 16GB DDR3&lt;/li&gt;
&lt;li&gt;Storage: 4TB Samsung 870 QVO&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Side-note: I also went ahead and replaced the thermal paste with a bootleg Honeywell PTM7950 phase-changing
thermal pad. It works just as well as fresh thermal paste, but hopefully it won&amp;rsquo;t drip out if the laptop is running
up-right, in a vertical laptop stand.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-adapter-1.jpg"&gt;
        &lt;img alt="Side-view of the EXP GDC Beast adapter." height="600" src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-adapter-1_hu_3a43b5b1b9c03cff.jpg" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Side-view of the EXP GDC Beast adapter.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-adapter-2.jpg"&gt;
        &lt;img alt="Top-down view." height="600" src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-adapter-2_hu_a2e31de3143dd378.jpg" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Top-down view.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-adapter-3.jpg"&gt;
        &lt;img alt="Backside of the adapter, with the AMD RX 480 attached." height="600" src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-adapter-3_hu_553fa04bd0356f91.jpg" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Backside of the adapter, with the AMD RX 480 attached.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-closeup.jpg"&gt;
        &lt;img alt="The extra power cable was barely long enough to fit." height="600" src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-closeup_hu_6d93a80ff5c6b047.jpg" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      The extra power cable was barely long enough to fit.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;h3 id="testing"&gt;Testing&lt;/h3&gt;
&lt;p&gt;My testing with the GTX 1650 was brief, as the open source &lt;code&gt;nouveau&lt;/code&gt; driver crashed on the Wayland desktop&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;, the proprietary
drivers under Fedora did not seem to work, and under Windows
the NVIDIA driver intentionally triggers the &lt;code&gt;error code 43&lt;/code&gt; issue. &lt;a href="https://egpu.io/forums/expresscard-mpcie-m-2-adapters/script-nvidia-error43-fixer/"&gt;A tweak exists for the Windows issue,&lt;/a&gt; but
that didn&amp;rsquo;t help much either.&lt;/p&gt;
&lt;p&gt;The AMD Radeon RX 480 seems to work well out of the box on Windows 11, plug-and-play. However, it wasn&amp;rsquo;t that stable,
but that could be related to the possibly faulty GPU itself or the power supply that can barely drive it under load.
Or the fact that Windows itself was running off of a 128GB Samsung USB 3.0 flash drive. Probably the latter, given that the
USB stick died as a result of this experiment.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-win11.jpg"&gt;
        &lt;img alt="Microsoft does not approve of this setup for at least 3 reasons." height="600" src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-win11_hu_64e4574d87c8199b.jpg" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Microsoft does not approve of this setup for at least 3 reasons.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;On Fedora Linux 40, I discovered that eGPU-s aren&amp;rsquo;t really plug and play. Sure, the image is there, but on my
ultrawide monitor I saw a whopping 11 frames per second! Turns out that by default GNOME on Wayland doesn&amp;rsquo;t like
to use the eGPU as the main rendering device, even on more legitimate Thunderbolt-based eGPU setups.
It&amp;rsquo;s a common enough problem that there exist &lt;a href="https://github.com/hertg/egpu-switcher"&gt;multiple&lt;/a&gt; &lt;a href="https://github.com/ewagner12/all-ways-egpu"&gt;solutions&lt;/a&gt; to this issue.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t like any of them&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;, so I disabled the &lt;code&gt;i915&lt;/code&gt; kernel driver responsible for driving the iGPU, which did the trick.
The 3440x1440p ultrawide monitor was being rendered by the eGPU and for the most part the experience was very smooth.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-linux.jpg"&gt;
        &lt;img alt="It's booting Linux!" height="600" src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-linux_hu_8b1035acfde21f1d.jpg" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      It's booting Linux!
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;The iGPU in the T430 can also run the ultrawide monitor at 60 Hz via the DisplayPort port on the dock, but it can&amp;rsquo;t
run things at 60 FPS at all times because it&amp;rsquo;s really, really weak.&lt;/p&gt;
&lt;p&gt;The eGPU ran things much smoother, which made the whole setup feel more responsive
and great to use. It&amp;rsquo;s not the fastest setup in the world, but the GUI running smoothly certainly makes it feel like one.&lt;/p&gt;
&lt;p&gt;I didn&amp;rsquo;t have a ton of games installed for testing, but I did give Minecraft a try, and it ran just fine. The CPU was
struggling when building new parts of the map, but the visuals were doing fine and I could see myself playing on this
setup for hours.&lt;/p&gt;
&lt;p&gt;There were some things that weren&amp;rsquo;t running smoothly even with the eGPU. For example, 1080p H.265 video playback was awfully
choppy. Using &lt;code&gt;radeontop&lt;/code&gt; revealed around 80% GPU usage, so either the decoding engine is crap on this GPU, or the very limited
PCIe bandwidth and CPU-GPU data transferring is the culprit.&lt;/p&gt;
&lt;p&gt;I also tested LibreELEC to see how it handles the eGPU and I&amp;rsquo;m happy to report that it renders Kodi on it by default,
with the UI running very smoothly.&lt;/p&gt;
&lt;p&gt;On Linux I also saw some stability issues and crashes, but those could also be down to the faulty GPU or the PSU
being underpowered. I can probably rule out the latter by limiting the GPU clock speeds, and &lt;a href="https://wiki.archlinux.org/title/AMDGPU#Manually"&gt;the Arch wiki&lt;/a&gt;
has some great instructions for that. Those toggles do work, but I noticed that GNOME can be a bit choppier when the GPU
switches between lower power states to higher ones.&lt;/p&gt;
&lt;p&gt;The physical stability of this setup is questionable, especially if you use a bigger GPU. Most prefabricated cases
for this eGPU adapter assume that you&amp;rsquo;re running it with an ATX/SFX PSU, so those are out of the question for me.
There exist some 3D printable designs out there, but they can sometimes be very specific to a particular setup or GPU,
so I&amp;rsquo;d have to design my own.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-setup-2.jpg"&gt;
        &lt;img alt="Work in progress." height="600" src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-setup-2_hu_b1899abd6d5e12ea.jpg" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      Work in progress.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-setup-3.jpg"&gt;
        &lt;img alt="I really like the look of the GPU, no need to worry about a case. Unless you have cats, which I do have. Uh-oh." height="600" src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-setup-3_hu_c1dbdb723078859f.jpg" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      I really like the look of the GPU, no need to worry about a case. Unless you have cats, which I do have. Uh-oh.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;








  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-setup-1.jpg"&gt;
        &lt;img alt="The final setup." height="600" src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-setup-1_hu_fc4671da42ee57b.jpg" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      The final setup.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;The power consumption of this setup isn&amp;rsquo;t great, unfortunately. Using a smart plug I measured the idle power of the
eGPU component to be about 20W. Typical desktop usage and video playback results in around 50W and peaks around 120-130W,
which is right on the limit of the shoddy PSU I&amp;rsquo;m using.&lt;/p&gt;
&lt;p&gt;If I had the choice to pick any GPU for this setup, then I&amp;rsquo;d likely opt for a modern AMD GPU that didn&amp;rsquo;t require additional
power via the 6/8 pin power connector as that option would likely be a lot more efficient while yielding similar performance.&lt;/p&gt;
&lt;p&gt;I ended up disassembling the setup in the end, partly because I
misdiagnosed &lt;a href="https://gitlab.freedesktop.org/mesa/mesa/-/issues/11566"&gt;a Mesa bug&lt;/a&gt; to be a GPU issue,
and because the RX 480 really was acting weird from time to time, even with LibreELEC.&lt;/p&gt;







  




&lt;figure class="center"&gt;
    
    &lt;a href="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-error.jpg"&gt;
        &lt;img alt="That can't be good." height="600" src="https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/media/egpu-error_hu_a54f1001824869bc.jpg" style="width: auto; height: auto; border-radius: 8px;" width="800" /&gt;
    &lt;/a&gt;
    &lt;figcaption class="center"&gt;
      That can't be good.
    &lt;/figcaption&gt;
    
&lt;/figure&gt;

&lt;p&gt;Not all experiments end up being successful.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;I think that these types of eGPU adapters are great even in 2024, but only for a small number of very specific use cases,
and with GPU-s that actually work.&lt;/p&gt;
&lt;p&gt;If you already have an older laptop around, and a compatible spare GPU collecting dust, then this setup will make sense for
desktop or media playback machines, and perhaps gaming if your demands are not very high.&lt;/p&gt;
&lt;p&gt;I don&amp;rsquo;t suggest building this setup from scratch, it&amp;rsquo;s probably not worth the money and hassle. Used gaming PC-s
that have similar specifications to this eGPU build, but with no funny PCIe bandwidth limitations, go for less than
200 EUR at this point.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;&lt;code&gt;nouveau&lt;/code&gt;? More like &lt;code&gt;novideo&lt;/code&gt;, am I right?&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;not that they&amp;rsquo;re bad, they are just made for a different type of setup.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>./techtipsy</author><pubDate>Wed, 07 Aug 2024 06:00:00 GMT</pubDate><guid isPermaLink="true">https://ounapuu.ee/posts/2024/08/07/thinkpad-t430-egpu/</guid></item><item><title>Game of Firsts</title><link>https://michael.mior.ca/blog/game-of-firsts/</link><description>&lt;p&gt;A lot of things seem to serve to unintentionally &lt;a href="https://xkcd.com/356/"&gt;nerd snipe&lt;/a&gt; me.
Over the weekend, a friend introduced me to &lt;a href="https://www.initialsgame.com/"&gt;The Initials Game&lt;/a&gt;.
The gist of it is that you are given two letters and a series of clues.
The goal is to guess a two-word phrase that starts with the given letters.
It’s a pretty entertaining game and I wondered how easy it would be to generate such puzzles.&lt;/p&gt;
&lt;p&gt;The next day I was able to hack something together in a couple hours that actually worked reasonably well.
The entire thing is based around &lt;a href="https://ollama.com/"&gt;Ollama&lt;/a&gt; since I wanted to be able to run locally.
I started off with a system prompt&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;You are a puzzle designer who is going to help create a word puzzle. Any pieces of the puzzle you generate should be short and simple and easily understandable to the average English-speaking adult anywhere in the world.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then I split the remainder into two prompts.
Given two letters, the first prompt asks for a phrase to be used in the puzzle.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;I will give you two letters and then you will think of a very simple two word phrase that starts with those two letters. For example, if I give you the letters C and F, you might pick Correctional Facility. For the letters P and T, you might select Party Trick. Respond with only the two word phrase and nothing else. The letters are {letter1} and {letter2}.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The second prompt asks for clues given this phrase.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;I am going to give you a two word phrase and I would like you to devise five very short clues (three or four words maximum) that will help someone guess the phrase. The first clue should be very vague and subsequent clues should get increasingly specific. Do not use any of the words from the phrase anywhere in any of the clues or elsewhere in your response. Output should be a simple numbered list in Markdown format. The two word phrase is &amp;quot;{phrase}&amp;quot;.&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;I later made a few further tweaks to these prompts, but this is the gist of it.
At this point, I was sometimes getting some reasonable phrases, but sometimes phrases that didn’t make sense at all.
Sometimes the letters in the phrase didn’t match the letters that were asked for.
But the biggest problem was that sometimes the phrases generated were pretty nonsensical, for example, “Bird Age.”
To solve this, I ended up using &lt;a href="https://ngrams.dev/"&gt;NGRAMS&lt;/a&gt; which is a REST API for querying the Google Books n-gram dataset.
This allows easily checking for how common the phrase is.
For example, “Bird Age”, appears only 269 times, while the much more reasonable phrase “Business Association” appears 200,334 times.
The solution was to generate multiple phrases until one meets an arbitrarily-determined threshold of popularity.&lt;/p&gt;
&lt;p&gt;Up until this point, I was just printing all the puzzles out via text.
But it’s a bit more fun to have them read out.
I ended up using &lt;a href="https://github.com/synesthesiam/opentts"&gt;OpenTTS&lt;/a&gt; to generate audio from the puzzles.
One unexpected problem is that coqui-tts, the actually speech system used, seems to have a real problem pronouncing letters when just written out as single letters.
For example, here’s what I got when I tried to have it say “The letters are E and A.”&lt;/p&gt;
&lt;p&gt;&lt;audio controls="controls" src="ae.mp3"&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;To solve this, I wrote out phonetic spellings of each letter, tweaking until each one sounded right.
If I instead generate audio for “The letters are Eeh and Ae,” I get the following much more reasonable output.&lt;/p&gt;
&lt;p&gt;&lt;audio controls="controls" src="ae2.mp3"&gt;&lt;/audio&gt;&lt;/p&gt;
&lt;p&gt;I first started with requiring the user to specify which letters to generate.
When switching to randomly picking letters, a uniform distribution doesn’t really work well.
English words aren’t randomly distributed so it makes sense to match this frequency.
To do this, I instead pick letters randomly but weighted based on the frequency of the first letters of English words.
To avoid picking a lot of double letters, I also halve the probability of the first letter when generating the second letter.&lt;/p&gt;
&lt;p&gt;Finally, here’s an example of a complete puzzle.
The code is available &lt;a href="https://github.com/michaelmior/game-of-firsts"&gt;on GitHub&lt;/a&gt;.
Not sure if I’ll keep working on this project further, but it’s pretty impressive what you can quickly accomplish these days with a bit of use of AI.&lt;/p&gt;
&lt;p&gt;&lt;audio controls="controls" src="puzzle.mp3"&gt;&lt;/audio&gt;&lt;/p&gt;</description><author>Michael Mior</author><pubDate>Wed, 07 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://michael.mior.ca/blog/game-of-firsts/</guid></item><item><title>Why I don't live in Hungary</title><link>https://bytepawn.com/why-i-dont-live-in-hungary.html</link><description>&lt;p&gt;In February 2016, I made a life-changing decision to leave Hungary and seek opportunities abroad, starting in London and later moving to Dubai. This essay explores the multitude of reasons behind my choice and why I have no immediate plans to return to Hungary.&lt;br /&gt;&lt;br /&gt; &lt;img alt="Orban no migration" src="/images/no-migration.jpg" style="width: 400px;" /&gt;&lt;/p&gt;</description><author>Bytepawn - Marton Trencseni</author><pubDate>Wed, 07 Aug 2024 01:00:00 GMT</pubDate><guid isPermaLink="true">https://bytepawn.com/why-i-dont-live-in-hungary.html</guid></item><item><title>“How to let go: one life ends while another begins”</title><link>https://jakeseliger.com/2024/08/06/how-to-let-go-one-life-ends-while-another-begins/</link><description>&amp;#8220;How to let go: one life ends while another begins. I&amp;#8217;m seven months pregnant with our daughter as Jake&amp;#8217;s life comes to a close. How do I walk into an uncertain future without him?&amp;#8220;</description><author>The Story's Story</author><pubDate>Tue, 06 Aug 2024 21:11:16 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/08/06/how-to-let-go-one-life-ends-while-another-begins/</guid></item><item><title>The Prototype Maturity Model</title><link>https://blog.andyglassman.com/2022/08/the-prototype-maturity-model.html</link><description>&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3Qa-LwnYKA9_14X2SE_RtwVpn9hMEyOZ4jLF5uHhy1dzSw9YR45zLBtAHJgLP_jZKMaZ7k4O2hEdP8DTPu56U6_lISgD7CdfJ3__7EWvo6kbr39ptFBNfOOI65Lfu3gYE-bmRWjxxP8iMM4w1_mYm12BWQQYYKRajffROSIfQFDHWqG9I964zdQ/s1024/robot%20evolve.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;br /&gt;&lt;/a&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3Qa-LwnYKA9_14X2SE_RtwVpn9hMEyOZ4jLF5uHhy1dzSw9YR45zLBtAHJgLP_jZKMaZ7k4O2hEdP8DTPu56U6_lISgD7CdfJ3__7EWvo6kbr39ptFBNfOOI65Lfu3gYE-bmRWjxxP8iMM4w1_mYm12BWQQYYKRajffROSIfQFDHWqG9I964zdQ/s1024/robot%20evolve.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;br /&gt;&lt;/a&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3Qa-LwnYKA9_14X2SE_RtwVpn9hMEyOZ4jLF5uHhy1dzSw9YR45zLBtAHJgLP_jZKMaZ7k4O2hEdP8DTPu56U6_lISgD7CdfJ3__7EWvo6kbr39ptFBNfOOI65Lfu3gYE-bmRWjxxP8iMM4w1_mYm12BWQQYYKRajffROSIfQFDHWqG9I964zdQ/s1024/robot%20evolve.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img alt="&amp;quot;software evolving like darwin evolution of robot pencil sketch&amp;quot;" border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3Qa-LwnYKA9_14X2SE_RtwVpn9hMEyOZ4jLF5uHhy1dzSw9YR45zLBtAHJgLP_jZKMaZ7k4O2hEdP8DTPu56U6_lISgD7CdfJ3__7EWvo6kbr39ptFBNfOOI65Lfu3gYE-bmRWjxxP8iMM4w1_mYm12BWQQYYKRajffROSIfQFDHWqG9I964zdQ/w200-h200/robot%20evolve.png" title="Software Evolution" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;h2 style="text-align: left;"&gt;Moving Fast, Taking Shortcuts&lt;/h2&gt;&lt;p&gt;Not all ideas are good ideas.&amp;nbsp; In fact, the majority of your ideas will not be good.&amp;nbsp; When it comes to building software, it doesn't make much sense to build out a prototype of a new idea with all the best practices a full fledged software product requires.&amp;nbsp; &amp;nbsp;This is why we rapidly prototype software for a new feature, or product.&amp;nbsp; We want to validate the idea as quickly as possible. This is the whole philosophy behind the minimal viable product or MVP that start ups are always talking about.&amp;nbsp; But MVP doesn't just apply to startups.&amp;nbsp; There are ideas that can be prototyped in the biggest companies that could save the company employees hundreds of hours of time, or millions of dollars.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;So, you decide your idea might have some merit. You meet with the team, and list out the core, minimal features.&amp;nbsp; Everyone is on board, and your team has the green light to build a prototype.&amp;nbsp; &amp;nbsp;Now, the hard decisions start to pile up.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioqFBEjRLYhh8F50ed6Ox2Hq9BxaxUH_Qw19Lntxhr1AienBSk8eJz2aGKsZCTPFuscMtLk4utioNoLCyErmsEHSOGmdYiIUxBuv_h2X4-UxEHcVzYtitjwHxtGl3ylpcDEMay071WrxVxKjPOlqFHOduDNAXlbBmms40HrPLfAfsuRHEFhcvGqQ/s1024/dna.png" style="clear: right; float: right; margin-bottom: 1em; margin-left: 1em;"&gt;&lt;img alt="software dna watercolor" border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEioqFBEjRLYhh8F50ed6Ox2Hq9BxaxUH_Qw19Lntxhr1AienBSk8eJz2aGKsZCTPFuscMtLk4utioNoLCyErmsEHSOGmdYiIUxBuv_h2X4-UxEHcVzYtitjwHxtGl3ylpcDEMay071WrxVxKjPOlqFHOduDNAXlbBmms40HrPLfAfsuRHEFhcvGqQ/w200-h200/dna.png" title="Software DNA Watercolor" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;It's tempting to over-engineer.&amp;nbsp; Our developer DNA is encoded to produce secure, scalable, and performant software.&amp;nbsp; When trying to get a prototype out the door, &lt;b&gt;there will be many decisions where we seemingly go against our better judgement&lt;/b&gt;.&amp;nbsp; &amp;nbsp;This is expected, but these decisions are not typically documented and revisited.&amp;nbsp;&amp;nbsp;&lt;/p&gt;&lt;p&gt;You take shortcuts in some areas.&amp;nbsp; Maybe you are storing data on disk, and not backing it up. Maybe the UI is bare bones.&amp;nbsp; Maybe there is one hard coded user that uses basic authentication.&amp;nbsp; It doesn't matter what the shortcuts are in each specific case.&amp;nbsp; The prototype has been released into the wild at a fraction of the time and cost it would have been to develop it to your highest standards. You keep iterating, and your prototype starts to resonate with the intended audience.&amp;nbsp; &lt;b&gt;This is a good thing.&amp;nbsp; &lt;/b&gt;The idea is validated, and your team prepares to launch the prototype as an internal tool, or maybe a customer facing feature or product.&lt;/p&gt;&lt;p&gt;You may lose control of your prototype. It may get co-opted or inherited by another team.&amp;nbsp; The team may start to receive pressure from management or sales to push a product out the door faster than was originally expected.&amp;nbsp;&lt;b&gt;This is still a good thing.&amp;nbsp;&lt;/b&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;With your prototype getting more eyeballs and use, questions start to appear.&amp;nbsp; Why did you choose that datatype?&amp;nbsp; Why didn't you follow our best practice guide for logging and observability?&amp;nbsp; Why is there no admin interface for the support team? After you've been through this once or twice, you start to pad your estimates, and defensively code.&amp;nbsp; The time to get an MVP out the door gets longer, and your ability to iterate quickly suffers.&amp;nbsp; This is a defensive CYA (cover your ass) approach.&amp;nbsp; If your prototype was inherited by another group, and they question your decisions, you could gain a reputation for writing poor code, or making poor design decisions.&amp;nbsp; You may even get defensive about your decisions and find yourself explaining the history of the project over and over, to various people in different roles.&amp;nbsp;&lt;b&gt;This is a bad thing.&lt;/b&gt;&lt;/p&gt;&lt;h2 style="text-align: left;"&gt;Communication&lt;/h2&gt;&lt;p&gt;After seeing situations like this arise in various companies of various sizes, I drafted the &lt;b&gt;Prototype Maturity Model&lt;/b&gt;.&amp;nbsp; You can think of it like a grading rubric that you might have received in primary school.&amp;nbsp; The twist is that there are no "bad" grades.&amp;nbsp; &amp;nbsp;The model is simply a tool that you and your team use to stay on the same page.&amp;nbsp; When you are in the initial phases of developing an MVP, you can look at the rubric and decide which areas you want to focus on, and which areas you can skimp on.&amp;nbsp; Every few weeks, you and the team can grade your prototype.&amp;nbsp; &lt;b&gt;This gives you a snapshot of the prototype maturity over time.&lt;/b&gt;&lt;/p&gt;&lt;p&gt;Now, assume your prototype has been handed off to another team without much notice.&amp;nbsp; You can point them to the model, and your ongoing snapshots of its maturity.&amp;nbsp; &lt;b&gt;This allows them to understand the decisions you made without necessarily listing each and every one.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;&lt;p&gt;The PMM also works great as a way to discuss risk tolerance with managers.&amp;nbsp; The model brings to light the gaps a software product may have.&amp;nbsp; It also plainly lays out the path to all stakeholders of what must be done in order to get the prototype within everyone's risk tolerance before a full launch.&amp;nbsp; &amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxCkRfd3vqjyGn-lnolqTqLGA_v9iZQfPIXvqN2obwQdPkBxwDa7PYrUBc1g1lVH9SGeRJXcc577UQktqYtttzddAX12fNt3VO_Mwlil8C1q1boQ_nNftvKM-o7tZRWF2UQ7cFIgxSmUHW7jzrjq0NZnfG0CY3cpDt95ncKQNdkfJrMOYd3ps32w/s1024/grade.png" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="200" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjxCkRfd3vqjyGn-lnolqTqLGA_v9iZQfPIXvqN2obwQdPkBxwDa7PYrUBc1g1lVH9SGeRJXcc577UQktqYtttzddAX12fNt3VO_Mwlil8C1q1boQ_nNftvKM-o7tZRWF2UQ7cFIgxSmUHW7jzrjq0NZnfG0CY3cpDt95ncKQNdkfJrMOYd3ps32w/w200-h200/grade.png" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;Take a look a the aspects and ratings below, or &lt;a href="https://docs.google.com/spreadsheets/d/1o9L4egOPOane3ow1HkE1PVv2kZh7mWTYJ7BcFUi0YKQ/edit?usp=sharing" target="_blank"&gt;view it in Google Docs&lt;/a&gt;&amp;nbsp;in matrix form. (Please, feel free to copy, and modify as you see fit!)&lt;/p&gt;&lt;p&gt;One or more engineers can give their opinion, and reasons for their rating should be detailed.&amp;nbsp; This will help them know what rating to give when revisiting on a regular basis, or if another team picks up the project.&lt;/p&gt;&lt;h2 style="text-align: left;"&gt;&lt;br /&gt;&lt;/h2&gt;&lt;div&gt;Let me know what you think!&amp;nbsp; If you have any questions about the model, or thoughts / insights / shared frustration with this sort of thing, let me know!&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;- Andy Glassman&lt;/div&gt;&lt;div&gt;&lt;a href="https://www.linkedin.com/in/andyglassman/"&gt;LinkedIn&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="https://twitter.com/a_glassman"&gt;Twitter&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h2 style="text-align: left;"&gt;The Prototype Maturity Model (PMM)&lt;/h2&gt;&lt;p&gt;The PMM is a matrix of aspects and ratings.&amp;nbsp; The aspects are broad categories of concerns that you typically encounter when developing software.&amp;nbsp; Ratings are a number from 0-3, with a short description of what the rating means.&amp;nbsp; &amp;nbsp;The aspects and ratings can be determined by you and your team, there is no "one size fits all" model.&lt;/p&gt;&lt;p&gt;Here are the aspects and ratings that I have personally used for various projects:&lt;/p&gt;&lt;p&gt;Note - I prefer to use 0-3, but it was just easier to used a numbered list :)&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Security&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Security of the application has not been considered.&lt;/li&gt;&lt;li&gt;Security of the application has been considered, but there are significant gaps identified to bring it in line with industry standards.&lt;/li&gt;&lt;li&gt;Security of the application has been analyzed internally, and major gaps have been closed.&lt;/li&gt;&lt;li&gt;Security of the application has been implemented to industry standards, and has been tested by a 3rd party.&amp;nbsp; Security testing by a 3rd party is done on a regular basis.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Observability&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Observability of the application has not been considered.&lt;/li&gt;&lt;li&gt;&amp;nbsp;System is observable in manual, and tedious ways, such as remote shell sessions.&amp;nbsp; May require logging into a specific environment.&lt;/li&gt;&lt;li&gt;&amp;nbsp;System is observable using ancillary tools such as aggregated logging, and it’s easy to search across services and environments.&lt;/li&gt;&lt;li&gt;System is observable, and utilizes distributed tracing across services, and includes infrastructure, cloud based services.&lt;/li&gt;&lt;/ol&gt;&lt;h3 style="text-align: left;"&gt;Audit&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Auditability of the application has not been considered.&lt;/li&gt;&lt;li&gt;&amp;nbsp;Key aspects of the system have been identified for auditability, but may require manual data querying.&lt;/li&gt;&lt;li&gt;Key aspects of the system have been identified for auditability, and there is an easy / secure way for internal employees to access the audit information.&lt;/li&gt;&lt;li&gt;Key aspects of the system are auditable, and there is an easy / secure way to access the records.&amp;nbsp; Records have a retention policy identified, and the archival process is automated. Retrieval of archived records is possible.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Support&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Support of the application has not been considered.&lt;/li&gt;&lt;li&gt;&amp;nbsp;Support is mostly done by engineers in an ad-hoc manner.&amp;nbsp; May require direct access to server instances, and direct credentials to the database.&lt;/li&gt;&lt;li&gt;Key customer facing support interactions have been identified, and there is a documented process to perform them.&amp;nbsp; Engineers not the only people who can perform the support.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Key customer facing support interactions can be easily performed by support employee.&amp;nbsp; Does not require direct access to servers, or database.&amp;nbsp; Modifications done on behalf of another user are recorded for auditability.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Support Monitoring&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Support Monitoring for the application has not been considered.&lt;/li&gt;&lt;li&gt;The application is mainly monitored ad-hoc by engineers (checking logs / QA)&lt;/li&gt;&lt;li&gt;Issues are automatically captured and reported by a tool.&lt;/li&gt;&lt;li&gt;Issues are automatically captured, and engineers are alerted when an issue arises.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Performance&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Performance of the application has not been considered.&lt;/li&gt;&lt;li&gt;The projected usage of the system has been documented, but no performance / load testing have been completed.&lt;/li&gt;&lt;li&gt;A performance / load test has been established, and has been performed.&lt;/li&gt;&lt;li&gt;Performance / load testing is part of regression testing. SLAs on performance have been established, and are actively enforced.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Performance Monitoring&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Performance Monitoring of the application has not been considered&lt;/li&gt;&lt;li&gt;Key performance metrics have been identified, but no way to effectively measure them has been implemented.&amp;nbsp; (or vice versa)&lt;/li&gt;&lt;li&gt;Capability exists to monitor performance but key metrics have not been identified.&lt;/li&gt;&lt;li&gt;Capability to monitor key metrics exists, and key metrics are captured and available (dashboard / alerting)&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Availability / Scale&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Availability / Scale of the application has not been considered.&lt;/li&gt;&lt;li&gt;Availability and scaling SLAs have been identified, but not yet implemented.&lt;/li&gt;&lt;li&gt;Availability&amp;nbsp; SLAs have been created, and are actively monitored.&amp;nbsp; Scaling approaches have been identified.&lt;/li&gt;&lt;li&gt;Availability is actively monitored, and application can scale automatically under high demand (or has enough overhead to meet expected peak demand).&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Customer Data&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;How sensitive customer data is handled has not been considered.&lt;/li&gt;&lt;li&gt;&amp;nbsp;Sensitive customer data has been identified.&lt;/li&gt;&lt;li&gt;Sensitive customer data has been identified, and policies / practices have been implemented to keep it secure.&lt;/li&gt;&lt;li&gt;Processes have been documented for what to do if there is a leak of customer data.&amp;nbsp;&amp;nbsp;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Compliance&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Compliance requirements have not been considered.&lt;/li&gt;&lt;li&gt;Compliance research has been done, and possible compliance work has been identified.&lt;/li&gt;&lt;li&gt;The app complies with most necessary compliance.&amp;nbsp; Any compliance gaps are documented and exist in work backlog.&lt;/li&gt;&lt;li&gt;App is compliant with all all mandatory policies.&amp;nbsp; Process is in place to ensure continued compliance with those policies.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Infrastructure - Environments&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Infrastructure has not been considered.&lt;/li&gt;&lt;li&gt;App is deployable to one or more non-local environment.&lt;/li&gt;&lt;li&gt;Application is deployable to production.&lt;/li&gt;&lt;li&gt;New environments can be stood up quickly, and in an automated / repeatable fashion.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Infrastructure - Deployments&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Deployment has not been considered.&lt;/li&gt;&lt;li&gt;App is manually deployable to an environment.&lt;/li&gt;&lt;li&gt;App is automatically deployable to production, and a rollback process has been identified and exercised.&amp;nbsp; If a deploy causes downtime or interruptions to end users, these must be documented.&lt;/li&gt;&lt;li&gt;App is able to be deployed with zero downtime or interruption to end users.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Infrastructure - Security&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Security has not been considered.&lt;/li&gt;&lt;li&gt;Basic security has been considered, and implemented. (port blocking, IAM role evaluations)&lt;/li&gt;&lt;li&gt;Process for continual evaluation of infrastructure security is created, and performed.&amp;nbsp; VMs and libraries are patched in a timely manner if exploits are identified.&lt;/li&gt;&lt;li&gt;Automated testing/monitoring of infrastructure level security is implemented.3rd party evaluation of security is routinely done.&amp;nbsp; Software to monitor and alert on intrusions, or exploitable images / libraries is used.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Data - Backup / Restore&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;Backup and restore of data has not been considered.&lt;/li&gt;&lt;li&gt;Manual backups of data are available in a secure manner.&lt;/li&gt;&lt;li&gt;Automated backups are run in production, and a restore process has been documented.&lt;/li&gt;&lt;li&gt;Automated backups are available, and the restore process has been documented, and executed in a test environment.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;UI / UX&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;The user interface, and user experience have not been considered.&lt;/li&gt;&lt;li&gt;UI / UX has been initially designed.&lt;/li&gt;&lt;li&gt;UI / UX has had end user feed back, and some has been implemented.&amp;nbsp; Accessibility has been considered, and meets minimum WCAG guidelines.&lt;/li&gt;&lt;li&gt;A process for continuous UI/UX improvement exists, and is exercised.&amp;nbsp; WCAGs are met, and continually re-evaluated by a 3rd party.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;h3 style="text-align: left;"&gt;Quality Assurance&lt;/h3&gt;&lt;p&gt;&lt;/p&gt;&lt;ol style="text-align: left;"&gt;&lt;li&gt;The quality of the application has not been considered.&lt;/li&gt;&lt;li&gt;Basic unit / integration tests exist.&amp;nbsp;&lt;/li&gt;&lt;li&gt;Majority of QA is manually performed by engineers / other employees.&lt;/li&gt;&lt;li&gt;Some QA is automated, mostly happy path flows. Manual QA is managed by a test case / test suite manager like Test Rail for repeatability. Regression and key user flows have automated test suites that alert / block deploys on failure.&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</description><author>Milwaukee Maven</author><pubDate>Tue, 06 Aug 2024 17:58:05 GMT</pubDate><guid isPermaLink="true">https://blog.andyglassman.com/2022/08/the-prototype-maturity-model.html</guid></item><item><title>Underestimating Users</title><link>https://bastian.rieck.me/blog/2024/underestimating/</link><description>&lt;p&gt;Back when  I was still doing research in visualization, a field
concerned with making complex data sets understandable, our field was
haunted by the spectre of a ostensibly misunderstood tool: the Rainbow colour map.&lt;/p&gt;
&lt;p&gt;Assuming that most of my readers are unfamiliar with this, here is
a brief and incomplete introduction to colour maps: The goal of a colour
map is to assign colours to data, so that pattern can emerge. For
instance, suppose you measure the temperature at some places in your
country. To show these temperatures on a map, you might pick colours
that range from &amp;lsquo;rather cold&amp;rsquo; to &amp;lsquo;rather hot.&amp;rsquo; In the parlance of our
research field, this would call for a &lt;em&gt;continuous colour map&lt;/em&gt;, since
temperature is (hopefully) a continuous quantity.&lt;/p&gt;
&lt;p&gt;Next to aesthetic choices and questions of colour-blindness, our field
used to be convinced&amp;mdash;and probably is&amp;mdash;that colour maps should be
chosen such that different gradations in consecutive colours are
perceptually &lt;em&gt;uniform&lt;/em&gt;. In other words, your perception of differences
in colours should roughly match the differences of their underlying
values. So far, so good! &lt;a href="https://www.nature.com/articles/s41467-020-19160-7"&gt;It turns out that algorithmically-generated
colour maps, i.e. colour maps that can be easily generated, are often
&lt;em&gt;not&lt;/em&gt; perceptually uniform&lt;/a&gt;.
Even worse (or so I believed): The default
choice of colour map in some common software packages was our enemy, the
notorious Rainbow colour map! Myriads of papers use(d) this colour map,
even though our research showed that the rainbow colour map introduced
patterns where none are. This led to all kinds of nice papers following
the &amp;lsquo;X considered harmful style,&amp;rsquo; such as work by &lt;a href="https://ieeexplore.ieee.org/document/4118486"&gt;Borland and
Taylor&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;At the time, I thought this was a pretty big deal,&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; and I would use
my signature look of superiority whenever I encountered such a paper. In
my head, I imagined scientists using this colour map to be uninformed,
being led astray to search for patterns where none exist.&lt;/p&gt;
&lt;p&gt;How wrong and presumptuous of me&amp;mdash;although one might show some
clemency and chalk it up to the usual exuberance and arrogance often
found in over-educated but not particularly wise persons. Now, with some
additional hindsight and having actually &lt;em&gt;talked&lt;/em&gt; to users of these
colour maps, my stance changed. I turns out that users are aware, at
least on an intuitive level, of the fact that these colour maps might
misrepresent things. Instead of treating the visualization as
a sacrosanct thing that showed truth&amp;mdash;as I was taught&amp;mdash;they instead
just treated it as another tool that helped them arrive at certain
hypotheses. However, they did not stop there, and would actually &lt;em&gt;test&lt;/em&gt;
any particular hypothesis about their data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In my arrogance, I had underestimated the users of our tools.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Now, with less hair but more wisdom, I see similar things happen in my
new field of research, viz. applications of machine learning. Here,
there is a vicious&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt; debate on the use of dimensionality-reduction
techniques like &lt;a href="https://umap-learn.readthedocs.io/en/latest/"&gt;UMAP&lt;/a&gt; or
&lt;a href="https://en.wikipedia.org/wiki/T-distributed_stochastic_neighbor_embedding"&gt;t-SNE&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Such techniques make it possible to visualise &lt;em&gt;some&lt;/em&gt; aspects of complex
high-dimensional data sets, and they have received enormous attention in
computational biology. A priori, when going from a high-dimensional
space to a low-dimensional one, it is clear that not everything can be
easily preserved. Thus, a two-dimensional plot cannot possibly preserve
all distances or all clusters in a meaningful fashion&amp;mdash;and some
computational biologists take umbrage at the fact that their colleagues
nevertheless use UMAP &amp;amp; friends without discussing their limitations.&lt;/p&gt;
&lt;p&gt;That is only part of the truth, though: It turns out that computational
biologists know quite well that dimensionality reduction distorts the
truth; they use the visualisations as a tool for hypothesis-generation
and also as a way to showcase some overall aspects  of their data, all
the while &lt;strong&gt;knowing full well&lt;/strong&gt; that some aspects are not preserved.&lt;/p&gt;
&lt;p&gt;The debate on whether to use or not use dimensionality reduction reminds
me very vividly of my righteous fight against rainbow colour maps, and
it appears to me that the vicious, sometimes even polemic, critiques of
UMAP and other dimensionality-reduction methods are as unwarranted as my
critiques of rainbow colour maps.&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;p&gt;Do not underestimate practitioners. They also know their stuff!
Moreover, as an epilogue, consider the saga of rainbow colour maps. The
wind appears to have shifted, and maybe, just maybe, &lt;a href="https://www.computer.org/csdl/magazine/cg/2023/03/10128890/1NdJMHqISnS"&gt;rainbow colour maps
are not that bad after all&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I hope we can say the same thing about dimensionality-reduction methods
at some point.&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;As the saying goes: Everything is such a pretty big deal in
academia because the stakes are so low&amp;hellip;&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Just go on Twitter.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Although, truth be told, I always just felt smug on the inside and
never started fights on social media.&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Ecce Homology on Bastian Grossenbacher Rieck's personal homepage</author><pubDate>Tue, 06 Aug 2024 17:00:00 GMT</pubDate><guid isPermaLink="true">https://bastian.rieck.me/blog/2024/underestimating/</guid></item><item><title>Solving Canceled Meeting Rooms With Apps Script</title><link>https://jerrynsh.com/google-kept-declining-my-meeting-room-heres-how-i-fix-it/</link><description>&lt;p&gt;Today, I arrived at work a bit earlier than usual. After grabbing my morning coffee routinely, I opened up my Google Calendar to see what meetings I had for the day. Of course, our meeting room for our recurring daily stand-up got canceled &lt;em&gt;again&lt;/em&gt;.&lt;/p&gt;&lt;figure class="kg-card kg-image-card kg-card-hascaption"&gt;&lt;img alt="A stand-up event on Google Calendar" class="kg-image" height="474" src="https://jerrynsh.com/content/images/2024/07/FLIP-Daily-Standup-GCal-Edit-1.png" width="811" /&gt;&lt;figcaption&gt;&lt;span style="white-space: pre-wrap;"&gt;This had been bothering me for&lt;/span&gt;&lt;/figcaption&gt;&lt;/figure&gt;</description><author>Jerry Ng</author><pubDate>Tue, 06 Aug 2024 03:00:41 GMT</pubDate><guid isPermaLink="true">https://jerrynsh.com/google-kept-declining-my-meeting-room-heres-how-i-fix-it/</guid></item><item><title>Fernando Serboncini</title><link>http://localhost/</link><description>Personal website of Fernando Serboncini - software developer, game creator, and writer from São Paulo, Brazil living in Montreal, Canada.</description><author>fserb.com</author><pubDate>Tue, 06 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">http://localhost/</guid></item><item><title>To The Metal... Compiling Your Own Language(s)</title><link>https://johnj.com/posts/to-the-metal/</link><description>&lt;p&gt;





&lt;a href="https://johnj.com/eclipse.jpg"&gt;&lt;img class="resize" src="https://johnj.com/eclipse_hu_4b980836f660ca22.jpg" style="width: 400px; border: 0px solid black;" /&gt;&lt;/a&gt;

&lt;/p&gt;
&lt;p&gt;
Like many programmers, I have programming &lt;a href="https://johnj.com/posts/in-defense-of-hobbies/"&gt;hobbies&lt;/a&gt;.  One of these is
implementing new languages.  My most recent language project, &lt;a href="https://github.com/eigenhombre/l1/"&gt;l1&lt;/a&gt;, was
a Lisp dialect whose primary data types are symbols and
arbitrarily-large integers.&lt;/p&gt;
&lt;p&gt;
I've been happy with &lt;code class="verbatim"&gt;l1&lt;/code&gt;, but it is interpreted; since I was actively
working on it last (2022), I've been wondering about the best way to
generate &lt;em&gt;compiled&lt;/em&gt; standalone executable programs, written in &lt;code class="verbatim"&gt;l1&lt;/code&gt; or
any other language.&lt;/p&gt;
&lt;div class="outline-2" id="outline-container-headline-1"&gt;
&lt;h2 id="headline-1"&gt;
The Problem In General
&lt;/h2&gt;
&lt;div class="outline-text-2" id="outline-text-headline-1"&gt;
&lt;p&gt;
Execution models for programming languages take three basic
approaches, listed in increasing order of speed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tree-walking interpreter&lt;/strong&gt;: Programs are read and parsed into ASTs
in memory, then executed step-by-step by an interpreter.  This
is the approach &lt;code class="verbatim"&gt;l1&lt;/code&gt; uses.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bytecode VM&lt;/strong&gt;: Programs are compiled into a sort of abstract
machine language, simpler than the physical processor's, and
executed by a virtual machine (VM).  Java and Python work this way.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Machine code generation&lt;/strong&gt;: The code is directly compiled into
machine language and executed on the user's hardware.  C and C++
programs work this way.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Languages using Option 2 often add just-in-time compilation to machine
code, for extra performance.  Option 3 is typically fastest, but is
sometimes skipped in introductory compiler classes and tutorials.  For
example, Robert Nystrom's excellent &lt;em&gt;&lt;a href="https://craftinginterpreters.com/"&gt;Crafting Interpreters&lt;/a&gt;&lt;/em&gt; book
devotes the first section to implementing a tree-walking interpreter
implementation in Java and the second half to a compiler and bytecode
VM written in C, with minimal coverage of how to target physical
hardware.  And the (also excellent) &lt;a href="https://dabeaz.com/compiler.html"&gt;class on compiler writing&lt;/a&gt; that I
took from David Beazley, in its first incarnation, stopped at the
point of generating of so-called intermediate representation (IR)
output (though students in the current iteration of the class do
compile to native code, using LLVM).&lt;/p&gt;
&lt;p&gt;
Compiling to machine code is tricky because CPUs are inherently
complex. Real hardware is intricate, cumbersome, and unintuitive if
you're primarily accustomed to high-level languages. Additionally,
there are numerous significant variants to consider (e.g., CPU/GPU,
ARM/Intel, 32-bit/64-bit architectures).&lt;/p&gt;
&lt;p&gt;
But targeting machine code rather than interpreters or bytecode VMs is
appealing, not just because it is an interesting challenge, but also
because the resulting artifacts are small, stand-alone, and typically
very fast.  While running Python, Ruby, and Java programs require the
appropriate infrastructure to be in place on the target machine at all
times, Go, Rust, and C programs (among others) benefit from targeting
the physical hardware: their programs tend to be smaller, and can be
deployed to identical computers simply by copying the executable file,
needing to deploy the interpreter, extra libraries, etc. to the target
machine(s).&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="outline-2" id="outline-container-headline-2"&gt;
&lt;h2 id="headline-2"&gt;
Small Is Beautiful
&lt;/h2&gt;
&lt;div class="outline-text-2" id="outline-text-headline-2"&gt;
&lt;p&gt;
As a programmer who came up during the dawn of personal computers, I
have some nostalgia for an era when programs or even entire operating
systems fit on a few-hundred-kB floppy disk.  Much existing software
feels bloated to me, though some widespread tools are still lean and
fast. For illustration purposes, here are the physical sizes of some
of the venerable command-line Unix programs I use on a daily basis
(this is on MacOS):&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Program&lt;/th&gt;
&lt;th class="align-right"&gt;Size (kB)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class="verbatim"&gt;wc&lt;/code&gt;&lt;/td&gt;
&lt;td class="align-right"&gt;100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class="verbatim"&gt;cat&lt;/code&gt;&lt;/td&gt;
&lt;td class="align-right"&gt;116&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class="verbatim"&gt;df&lt;/code&gt;&lt;/td&gt;
&lt;td class="align-right"&gt;116&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code class="verbatim"&gt;more&lt;/code&gt;&lt;/td&gt;
&lt;td class="align-right"&gt;360&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;
These were chosen more or less at random from my &lt;code class="verbatim"&gt;bash&lt;/code&gt; history and
are representative of old-school Unix utilities.  For comparison,
iMovie on my Mac is &lt;strong&gt;2.8 GB&lt;/strong&gt;, several thousand times larger than the
largest of these.  Of course, the comparison is somewhat ludicrous -
iMovie does many amazing things… but I use all the above programs
hundreds or thousands of times more often than I do iMovie, so it's
good that that they are compact and run quickly.  In a time of
increasingly bloated software stacks,
I find myself especially drawn to simple tools with small footprints.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="outline-2" id="outline-container-headline-3"&gt;
&lt;h2 id="headline-3"&gt;
An Approach
&lt;/h2&gt;
&lt;div class="outline-text-2" id="outline-text-headline-3"&gt;
&lt;p&gt;
If targeting physical hardware is hard, what tools can we use to make
the job easier?&lt;/p&gt;
&lt;p&gt;
I recently started learning about &lt;a href="https://llvm.org/"&gt;LLVM&lt;/a&gt;, a modular set of compiler
tools which "can be used to develop a frontend for any programming
language and a backend for any instruction set architecture"
(&lt;a href="https://en.wikipedia.org/wiki/LLVM"&gt;Wikipedia&lt;/a&gt;).  LLVM has been used heavily in the Rust toolchain and in
Apple's developer tools.&lt;/p&gt;
&lt;p&gt;
The "modular" adjective is critical here: LLVM is separated into
front-end, back-end and optimizing parts thanks to a shared
"&lt;a href="https://en.wikipedia.org/wiki/Intermediate_representation"&gt;intermediate representation&lt;/a&gt;" (IR) – a sort of portable assembly
language which represents simple computation steps in a
machine-independent but low-level manner.&lt;/p&gt;
&lt;p&gt;
The LLVM IR takes a little getting used to but, with a little
practice, is reasonably easy to read, and, more importantly, to
generate.&lt;/p&gt;
&lt;p&gt;
As an example, consider the following simple C program, &lt;code class="verbatim"&gt;three.c&lt;/code&gt;,
which stores the number 3 in a variable and uses it as its exit code.
We will use &lt;code class="verbatim"&gt;clang&lt;/code&gt;, the LLVM C/C++/Obj-C/… compiler for the LLVM
ecosystem:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat three.c
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int x = 3;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;int main() {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  return x;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ clang three.c -o three
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./three; echo $?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
One can easily view, and possibly even understand, the assembler
output for such a simple program:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ clang -O3 -S three.c -o three.s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat -n three.s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     1		.section	__TEXT,__text,regular,pure_instructions
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     2		.build_version macos, 14, 0	sdk_version 14, 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     3		.globl	_main                           ; -- Begin function main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     4		.p2align	2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     5	_main:                                  ; @main
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     6		.cfi_startproc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     7	; %bb.0:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     8	Lloh0:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     9		adrp	x8, _x@PAGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    10	Lloh1:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    11		ldr	w0, [x8, _x@PAGEOFF]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    12		ret
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    13		.loh AdrpLdr	Lloh0, Lloh1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    14		.cfi_endproc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    15	                                        ; -- End function
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    16		.section	__DATA,__data
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    17		.globl	_x                              ; @x
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    18		.p2align	2, 0x0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    19	_x:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    20		.long	3                               ; 0x3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    21
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    22	.subsections_via_symbols&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
In comparison, here is the LLVM IR for the same program:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ clang -S -emit-llvm three.c -o three.ll
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat -n three.ll
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     1	; ModuleID = 'three.c'
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     2	source_filename = "three.c"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     3	target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     4	target triple = "arm64-apple-macosx14.0.0"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     6	@x = global i32 3, align 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     7
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     8	; Function Attrs: noinline nounwind optnone ssp uwtable(sync)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     9	define i32 @main() #0 {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    10	  %1 = alloca i32, align 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    11	  store i32 0, ptr %1, align 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    12	  %2 = load i32, ptr @x, align 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    13	  ret i32 %2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    14	}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    15
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    16	attributes #0 = { noinline nounwind optnone ssp ;; .... very long list...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    17
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    18	!llvm.module.flags = !{!0, !1, !2, !3, !4}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    19	!llvm.ident = !{!5}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    21	!0 = !{i32 2, !"SDK Version", [2 x i32] [i32 14, i32 4]}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    22	!1 = !{i32 1, !"wchar_size", i32 4}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    23	!2 = !{i32 8, !"PIC Level", i32 2}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    24	!3 = !{i32 7, !"uwtable", i32 1}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    25	!4 = !{i32 7, !"frame-pointer", i32 1}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    26	!5 = !{!"Apple clang version 15.0.0 (clang-1500.3.9.4)"}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
There is a fair amount of stuff here, but a lot of it looks
suspiciously like metadata we don't really care about for our
experiments going forward.  The, uh, &lt;code class="verbatim"&gt;main&lt;/code&gt; region of interest is from
lines 9-14 – notice that the function definition itself looks a
little more readable than the assembly language version, but slightly
lower-level than the original C program.&lt;/p&gt;
&lt;p&gt;
You can turn the IR into a runnable program:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ clang -O3 three.ll -o three
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./three; echo $?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The approach I explore here is to &lt;em&gt;generate&lt;/em&gt; LLVM IR "by fair means or foul."
Here, let's just edit our IR down to something more minimal and see how it goes.
I suspect the &lt;code class="verbatim"&gt;store&lt;/code&gt; of &lt;code class="verbatim"&gt;0&lt;/code&gt; in "register" &lt;code class="verbatim"&gt;%1&lt;/code&gt; is gratuitous, so
let's try to remove it, along with all the metadata:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat 3.ll
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;target triple = "x86_64-apple-macosx14.0.0"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@x = global i32 3, align 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;define i32 @main() {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  %1 = load i32, ptr @x, align 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  ret i32 %1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ clang -O3 3.ll -o 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./3; echo $?
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
This is frankly not much more complicated than the C code, and it
shows a helpful strategy at work:&lt;/p&gt;
&lt;p&gt;
Step 1: To understand how to accomplish something in LLVM IR,
write the corresponding C program and use &lt;code class="verbatim"&gt;clang&lt;/code&gt; to generate
the IR, being alert for possible "extra stuff" like we saw
in the example.&lt;/p&gt;
&lt;p&gt;
Step 2. Try to generate, and test, working programs from the IR you
write or adapt, making adjustments as desired.&lt;/p&gt;
&lt;p&gt;
There is another, optional step as well:&lt;/p&gt;
&lt;p&gt;
Step 3. Use, or write, language "bindings" to drive LLVM generation
from the language of your choice.  This is the step we will consider next.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="outline-2" id="outline-container-headline-4"&gt;
&lt;h2 id="headline-4"&gt;
Enter Babashka
&lt;/h2&gt;
&lt;div class="outline-text-2" id="outline-text-headline-4"&gt;
&lt;p&gt;
While one can write LLVM IR directly, as we have seen, we are
interested in compiling other languages (possibly higher-level ones of
our own invention), so we will want to &lt;em&gt;generate&lt;/em&gt; IR somehow.  For
this project I chose &lt;a href="https://babashka.org/"&gt;Babashka&lt;/a&gt;, an implementation of the &lt;a href="https://clojure.org/"&gt;Clojure&lt;/a&gt;
programming language I have found ideal for small projects where both
start-up speed and expressiveness are important.&lt;/p&gt;
&lt;p&gt;
(I assume some familiarity with Lisp and Clojure in this post; for those
just getting started, &lt;a href="https://www.braveclojure.com/"&gt;Clojure for the Brave and True&lt;/a&gt; is a good introduction.)&lt;/p&gt;
&lt;p&gt;
The repo &lt;a href="https://github.com/eigenhombre/llbb"&gt;https://github.com/eigenhombre/llbb&lt;/a&gt; contains the source files
discussed here.  The bulk of the code in this repo is in &lt;a href="https://github.com/eigenhombre/llbb/blob/master/llir.bb"&gt;&lt;code class="verbatim"&gt;llir.bb&lt;/code&gt;&lt;/a&gt;, a
source file which provides alternative definitions in Clojure for
common LLVM idioms.  Some of these are trivial translations:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(def m1-target "arm64-apple-macosx14.0.0")
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn target [t] (format "target triple = \"%s\"" t))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
… whereas other expressions leverage the power of Clojure to a
greater degree.  For example, this section defines translations used
to represent arithmetic operations:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn arithm [op typ a b]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (format "%s %s %s, %s"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          (name? op)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          (name? typ)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          (sigil a)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          (sigil b)))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn add [typ a b] (arithm :add typ a b))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn sub [typ a b] (arithm :sub typ a b))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn mul [typ a b] (arithm :mul typ a b))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn div [typ a b] (arithm :sdiv typ a b))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(comment
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (div :i32 :a :b)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  ;;=&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "sdiv i32 %a, %b"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (add :i8 :x 1)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  ;;=&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "add i8 %x, 1")&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
You can see this approach at work by representing the C program
discussed earlier:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(module
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (assign-global :i :i32 3)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (def-fn :i32 :main []
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;   (assign :retval (load :i32 :i))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;   (ret :i32 :retval)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
which evaluates to&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;target triple = "arm64-apple-macosx14.0.0"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@i = global i32 3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;define i32 @main() nounwind {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  %retval = load i32, i32* @i, align 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  ret i32 %retval
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
I use here a slightly different but equivalent pointer syntax for the
&lt;code class="verbatim"&gt;load&lt;/code&gt; expression than output by &lt;code class="verbatim"&gt;clang&lt;/code&gt; in the example above.&lt;/p&gt;
&lt;p&gt;
Two very small helper functions allow me to test out small programs quickly:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(require '[babashka.process :as sh])
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  Use `bash` to run command(s) `s`, capturing both stdout/stderr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  as a concatenated string.  Throw an exception if the exit code
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  is nonzero.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  [s]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (let [{:keys [out err]}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        (sh/shell {:out :string, :err :string}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                  (format "bash -c '%s'" s))]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (str/join "\n" (remove empty? [out err]))))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
and&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(require '[babashka.fs :as fs])
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn compile-to
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  Save IR `body` to a temporary file and compile it, writing the
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  resulting binary to `progname` in the current working directory.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  [progname body]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (let [ll-file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        (str (fs/create-temp-file {:prefix "llbb-", :suffix ".ll"}))]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (spit ll-file body)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (sh (format "clang -O3 %s -o %s"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                ll-file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                progname))))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
These two together allow me to test small programs out quickly at the
REPL.  Some examples follow, obtained by running the C compiler for
equivalent programs, generating and inspecting the LLVM IR, and
translating them into new Clojure bindings as cleanly as possible.&lt;/p&gt;
&lt;p&gt;
&lt;strong&gt;Minimum viable program&lt;/strong&gt;: just return zero:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(compile-to "smallest-prog"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            (module
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;             (def-fn :i32 :main []
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;               (ret :i32 0))))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(sh "./smallest-prog; echo -n $?")
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;;=&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;"0"&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
&lt;strong&gt;Argument count&lt;/strong&gt;: return, as the exit code, the number of arguments,
including the program name:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;; Argument counting: return number of arguments as an exit code:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(compile-to "argcount"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            (module
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;             (def-fn :i32 :main [[:i32 :arg0]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                                 [:ptr :arg1_unused]]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;               (assign :retptr (alloca :i32))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;               (store :i32 :arg0 :ptr :retptr)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;               (assign :retval (load :i32 :retptr))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;               (ret :i32 :retval))))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(sh "./argcount; echo -n $?") ;;=&amp;gt; "1"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(sh "./argcount 1 2 3; echo -n $?") ;;=&amp;gt; "4"&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
&lt;strong&gt;Hello, world&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(let [msg "Hello, World."
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      n (inc (count msg))] ;; Includes string terminator
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (compile-to "hello"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;              (module
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;               (external-fn :i32 :puts :i8*)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;               (def-global-const-str :message msg)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;               (def-fn :i32 :main []
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                 (assign :as_ptr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                         (gep (fixedarray n :i8)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                              (star (fixedarray n :i8))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                              (sigil :message)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                              [:i64 0]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                              [:i64 0]))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                 (call :i32 :puts [:i8* :as_ptr])
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                 (ret :i32 0)))))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(sh "./hello") ;;=&amp;gt; "Hello, World.\n"&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
This is the first program one typically writes in a new programming
language.  Note that we use here idioms (&lt;code class="verbatim"&gt;external-fn&lt;/code&gt;, &lt;code class="verbatim"&gt;call&lt;/code&gt;) to
define and invoke an external function from the C standard library.&lt;/p&gt;
&lt;p&gt;
Let's see how big the resulting program is:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(sh "ls -l hello")
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;;=&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;"-rwxr-xr-x  1 jacobsen  staff  33432 Aug 14 21:09 hello\n"&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
At this point I want to pause to reconsider one of the points of this
exercise, which is to produce small programs. Here are the rough
executable sizes for a "Hello, World" example program in various
languages that I use frequently:&lt;/p&gt;
&lt;table&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Language&lt;/td&gt;
&lt;td&gt;Size&lt;/td&gt;
&lt;td class="align-right"&gt;Relative Size&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Common Lisp&lt;/td&gt;
&lt;td&gt;38 MB&lt;/td&gt;
&lt;td class="align-right"&gt;1151&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clojure&lt;/td&gt;
&lt;td&gt;3.4 MB&lt;/td&gt;
&lt;td class="align-right"&gt;103&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;1.9 MB&lt;/td&gt;
&lt;td class="align-right"&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;33 kB&lt;/td&gt;
&lt;td class="align-right"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLVM IR&lt;/td&gt;
&lt;td&gt;33 kB&lt;/td&gt;
&lt;td class="align-right"&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;
I threw Clojure in there for comparison even though, unlike the other
examples, the resulting überjar also requires a Java bytecode VM in
order to run.  The programs generated from C and from LLVM IR are
equivalent; this is not surprising, given that I used the C program to
guide my writing and translation of the LLVM IR.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="outline-2" id="outline-container-headline-5"&gt;
&lt;h2 id="headline-5"&gt;
Building A Compiling Calculator
&lt;/h2&gt;
&lt;div class="outline-text-2" id="outline-text-headline-5"&gt;
&lt;p&gt;
We are now ready to implement a "compiler" for something approaching a
useful language, namely, greatly reduced subset of &lt;a href="https://en.wikipedia.org/wiki/Forth_(programming_language"&gt;Forth&lt;/a&gt;.  Forth is a
stack-based language created in the 1970s and still in use
today, especially for small embedded systems.&lt;/p&gt;
&lt;p&gt;
LLVM will handle the parts commonly known as "compiler backend" tasks,
and Babashka will provide our "frontend," namely breaking the text into
tokens and parsing them.  This task is made easy for us, because Forth
is syntactically quite simple, and Babashka relatively powerful. Here are
the language rules we will adopt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Program tokens are separated by whitespace.&lt;/li&gt;
&lt;li&gt;Non-numeric tokens are math operators.&lt;/li&gt;
&lt;li&gt;Only integer operands are allowed.&lt;/li&gt;
&lt;li&gt;Comments begin with &lt;code class="verbatim"&gt;\\&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Forth expressions typically place the arguments first, and the operator
last (so-called "reverse-Polish notation").  Here is an example program
which does some math and prints the result:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(def example "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2 2 +  \\ 4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5 *    \\ multiply by five to get 20
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2 /    \\ divide by 2 -&amp;gt; 10
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-1 +   \\ add -1 -&amp;gt; 9
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;8 -    \\ subtract 8 -&amp;gt; 1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.      \\ prints '1'
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;")&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The code in &lt;a href="https://github.com/eigenhombre/llbb/blob/master/forth.bb"&gt;&lt;code class="verbatim"&gt;forth.bb&lt;/code&gt;&lt;/a&gt; handles the parser, whose goal is to consume the
raw program text and generate an abstract syntax tree (in our case, just
a list) of operations to translate into IR:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn strip-comments
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  Remove parts of lines beginning with backslash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  [s]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (str/replace s #"(?sm)^(.*?)\\.*?$" "$1"))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn tokenize
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  Split `s` on any kind of whitespace
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  [s]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (remove empty? (str/split s #"\s+")))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defrecord node
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    [typ val] ;; A node has a type and a value
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  Object
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (toString [this]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (format "[%s %s]" (:typ this) (:val this))))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;; Allowed operations
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(def opmap {"+" :add
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            "-" :sub
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            "/" :div
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            "*" :mul
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            "." :dot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            "drop" :drop})
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn ast
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  Convert a list of tokens into an \"abstract syntax tree\",
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  which in our Forth is just a list of type/value pairs.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  "
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  [tokens]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (for [t tokens
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;        :let [op (get opmap t)]]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (cond
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      ;; Integers (possibly negative)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      (re-matches #"^\-?\d+$" t)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      (node. :num (Integer. t))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      ;; Operations
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      op (node. :op op)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      :else (node. :invalid :invalid))))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
Running this on our example,&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(-&amp;gt;&amp;gt; example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     strip-comments
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     tokenize
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     ast
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     (map str))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;;=&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;("[:num 2]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:num 2]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:op :add]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:num 5]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:op :mul]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:num 2]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:op :div]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:num -1]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:op :add]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:num 8]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:op :sub]"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; "[:op :dot]")&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The remainder of &lt;code class="verbatim"&gt;forth.bb&lt;/code&gt; essentially just implements the needed
operators, as well as the required stack and the reference to the &lt;code class="verbatim"&gt;printf&lt;/code&gt;
C library function.  It is perhaps a bit lengthy to go through here in
its entirety, so I will share one example where Babashka helps:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn def-arithmetic-op [nam op-fn]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (def-fn :void nam []
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (assign :sp (call :i32 :get_stack_cnt))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (if-lt :i32 :sp 2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;           (els)  ;; NOP - not enough on stack
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;           (els
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            (assign :value2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                    (call :i32 :pop))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            (assign :value1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                    (call :i32 :pop))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            (assign :result (op-fn :i32 :value1 :value2))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;            (call :void :push [:i32 :result])))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (ret :void)))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
This makes LLVM IR which does the following, in pseudo-code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;get current stack position; ensure at least two entries (else return)&lt;/li&gt;
&lt;li&gt;pop the operands off the stack&lt;/li&gt;
&lt;li&gt;apply the arithmetic operator to the operands&lt;/li&gt;
&lt;li&gt;put the result on the stack&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Implementing the four arithmetic operators is then as simple
as invoking&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;; ...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(def-arithmetic-op :mul mul)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(def-arithmetic-op :add add)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(def-arithmetic-op :sub sub)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(def-arithmetic-op :div div)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;; ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
when generating the IR.&lt;/p&gt;
&lt;p&gt;
Aside from the general-purpose LLVM IR code (&lt;code class="verbatim"&gt;llir.bb&lt;/code&gt;), the Forth
implementation is under two hundred lines.  It includes the invocation
to &lt;code class="verbatim"&gt;clang&lt;/code&gt; to compile the temporary IR file to make the runnable
program.  Here's an example compilation session:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat example.fs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;       \\ initial state          stack: []
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3      \\ put 3 on stack.        stack: [3]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;99     \\ put 99 on stack.       stack: [3, 99]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;drop   \\ discard top item.      stack: [3]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;drop   \\ discard top item.      stack: []
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2 2    \\ put 2 on stack, twice: stack: [2, 2]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+      \\ 2 + 2 = 4.             stack: [4]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5 *    \\ multiply 4 * 5.        stack: [20]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2 /    \\ divide by 2.           stack: [10]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-1 +   \\ add -1                 stack: [9]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;8 -    \\ subtract 8 -&amp;gt; 1        stack: [1]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.      \\ prints '1'             stack: [1]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;drop   \\ removes 1.             stack: []
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./forth.bb example.fs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The resulting program is fast, as expected…&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ time ./example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;real	0m0.007s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;user	0m0.002s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sys	0m0.003s&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
… and small:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ls -l ./example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-rwxr-xr-x  1 jacobsen  staff  8952 Aug 16 09:52 ./example&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
Let's review what we've done: we have implemented a small subset of Forth,
writing a compiler front-end in Babashka/Clojure to translate source programs
into LLVM IR, and using &lt;code class="verbatim"&gt;clang&lt;/code&gt; to turn the IR into compact binaries.  The
resulting programs are small and fast.&lt;/p&gt;
&lt;p&gt;
Sensible next steps would be to implement more of Forth's stack
operators, and maybe start to implement the &lt;code class="verbatim"&gt;:&lt;/code&gt; (colon) operator,
Forth's mechanism for defining new symbols and functions.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="outline-2" id="outline-container-headline-6"&gt;
&lt;h2 id="headline-6"&gt;
Lisp
&lt;/h2&gt;
&lt;div class="outline-text-2" id="outline-text-headline-6"&gt;
&lt;p&gt;
Instead, let's implement a different variant of our arithmetic
calculating language, using Lisp syntax (&lt;a href="https://en.wikipedia.org/wiki/S-expression"&gt;S-expressions&lt;/a&gt;).  Consider our
first Forth example:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2 2 +
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5 *
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2 /
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-1 +
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;8 -
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
In Lisp, this looks like:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ cat example.lisp
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(print (- (+ -1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;             (/ (* 5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                   (+ 2 2))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                2))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          8))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
Rather than coming last, as they did before ("postfix" notation), our
operators come first ("prefix" notation). The order of operations is
determined by parentheses, as opposed to using stack as we did for
our Forth implementation.&lt;/p&gt;
&lt;p&gt;
Here Babashka helps us tremendously because such parenthesized prefix
expressions are valid &lt;a href="https://github.com/edn-format/edn"&gt;EDN&lt;/a&gt; data, which the Clojure function
&lt;code class="verbatim"&gt;clojure.edn/read-string&lt;/code&gt; can parse for us. But we need to convert the
resulting nested list into the "SSA" (&lt;a href="https://en.wikipedia.org/wiki/Static_single-assignment_form"&gt;single static assignment&lt;/a&gt;)
expressions LLVM understands. This is relatively straightforward with
a recursive function which expands leaves of the tree and stores the
results as intermediate values:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn to-ssa [expr bindings]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (if (not (coll? expr))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    expr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (let [[op &amp;amp; args] expr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          result (gensym "r")
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          args (doall
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                (for [arg args]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                  (if-not (coll? arg)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                    arg
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                    (to-ssa arg bindings))))]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      (swap! bindings conj (concat [result op] args))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      result)))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn convert-to-ssa [expr]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (let [bindings (atom [])]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (to-ssa expr bindings)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    @bindings))&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
We use &lt;code class="verbatim"&gt;gensym&lt;/code&gt; here to get a unique variable name for each assignment,
and &lt;code class="verbatim"&gt;doall&lt;/code&gt; to force the evaluation of the lazy for expansion of the
argument terms. The result:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(-&amp;gt;&amp;gt; "example.lisp"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     slurp
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     (edn/read-string)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;     convert-to-ssa)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;;;=&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[(r623 + 2 2)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (r622 * 5 r623)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (r621 / r622 2)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (r620 + -1 r621)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (r619 - r620 8)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; (r618 print r619)]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
The next step will be to actually write out the corresponding LLVM
IR. The rest of &lt;code class="verbatim"&gt;lisp.bb&lt;/code&gt; is satisfyingly compact.  Operators (we
have five, but more can easily be added), are just a map of symbols
to tiny bits of LLVM code:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(def ops
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  {'* #(mul :i32 %1 %2)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;   '+ #(add :i32 %1 %2)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;   '/ #(div :i32 %1 %2)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;   '- #(sub :i32 %1 %2)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;   'print
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;   #(call "i32 (i8*, ...)"
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          :printf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          [:i8* :as_ptr]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          [:i32 (sigil %1)])})&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
Similar to our Forth implementation, but even more compact, the main Babashka function,
after a brief setup for &lt;code class="verbatim"&gt;printf&lt;/code&gt;, generates a series of SSA instructions.&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(defn main [[path]]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;  (when path
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;    (let [assignments (-&amp;gt;&amp;gt; path
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                           slurp
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                           edn/read-string
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                           convert-to-ssa)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          outfile (-&amp;gt;&amp;gt; path
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                       fs/file-name
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                       fs/split-ext
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                       first)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;          ir (module
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;              (external-fn :i32 :printf :i8*, :...)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;              (def-global-const-str :fmt_str "%d\n")
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;              (def-fn :i32 :main []
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                (assign :as_ptr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                        (gep (fixedarray 4 :i8)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                             (star (fixedarray 4 :i8))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                             (sigil :fmt_str)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                             [:i64 0]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                             [:i64 0]))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                ;; Interpolate SSA instructions / operator invocations:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                (apply els
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                       (for [[reg op &amp;amp; args] assignments
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                             :let [op-fn (ops op)]]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                         (if-not op-fn
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                           (throw (ex-info "bad operator" {:op op}))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                           (assign reg (apply op-fn args)))))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;                (ret :i32 0)))]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;      (compile-to outfile ir))))
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;(main *command-line-args*)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
Putting these parts together (see &lt;a href="https://github.com/eigenhombre/llbb/blob/master/lisp.bb"&gt;&lt;code class="verbatim"&gt;lisp.bb&lt;/code&gt;&lt;/a&gt; on GitHub), we have:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./lisp.bb example.lisp
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ./example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
It, too, is small and fast:&lt;/p&gt;
&lt;div class="src src-text"&gt;
&lt;div class="highlight"&gt;&lt;pre class="chroma" tabindex="0"&gt;&lt;code class="language-text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ time ./example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;real	0m0.006s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;user	0m0.001s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sys	0m0.003s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ ls -al ./example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-rwxr-xr-x  1 jacobsen  staff  33432 Aug 16 20:52 ./example&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;
To say this is a "working Lisp compiler" at this point would be
grandiose (we still need functions, lists and other collection types,
eval, macros, …) but we have developed an excellent foundation
to build upon.&lt;/p&gt;
&lt;p&gt;
To summarize, the strategy we have taken is as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Use a high level language (in our case, Babashka/Clojure) to parse
input and translate into LLVM IR;&lt;/li&gt;
&lt;li&gt;When needed, write and generate small C programs to understand the
equivalent IR to generate.&lt;/li&gt;
&lt;li&gt;Compile the IR to small, fast binaries using &lt;code class="verbatim"&gt;clang&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="outline-2" id="outline-container-headline-7"&gt;
&lt;h2 id="headline-7"&gt;
Alternatives and Future Directions
&lt;/h2&gt;
&lt;div class="outline-text-2" id="outline-text-headline-7"&gt;
&lt;p&gt;
I should note that C itself has long been used as an intermediate
language, and we could have used it here instead of LLVM IR; I don't
have a strong sense of the tradeoffs involved yet, but wanted to take
the opportunity to learn more about LLVM for this project.&lt;/p&gt;
&lt;p&gt;
LLVM is interesting to me because of the modularity of its toolchain;
it also provides a JIT compiler which allows one to build and execute
code at run-time.  We didn't investigate tooling for that here (it
needs deeper LLVM language bindings than the homegrown Babashka code I
used), but it could provide a way to do run-time compilation similar
to what SBCL (a Common Lisp implementation which can compile functions
at run-time) does.&lt;/p&gt;
&lt;p&gt;
Here are some directions I'm considering going forward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Try interfacing with external libraries, e.g. a &lt;a href="https://en.wikipedia.org/wiki/Arbitrary-precision_arithmetic"&gt;bignum&lt;/a&gt; library;&lt;/li&gt;
&lt;li&gt;Implement more Forth functionality;&lt;/li&gt;
&lt;li&gt;Implement more Lisp, possibly including a significant subset of &lt;code class="verbatim"&gt;l1&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;Try a JIT-based approach, possibly using Rust as the host language.&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="outline-2" id="outline-container-headline-8"&gt;
&lt;h2 id="headline-8"&gt;
Conclusion
&lt;/h2&gt;
&lt;div class="outline-text-2" id="outline-text-headline-8"&gt;
&lt;p&gt;
Whenever possible, I want to make small, fast programs, and I like
playing with and creating small programming languages. LLVM provides a
fascinating set of tools and techniques for doing so, and using
Babashka to make small front-ends for IR generation turns out to be
surprisingly effective, at least for simple languages.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;</description><author>John Jacobsen</author><pubDate>Tue, 06 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://johnj.com/posts/to-the-metal/</guid></item><item><title>Notes on Zero-Knowledge Proofs and Secure Remote Password (SRP) Protocol</title><link>https://lambdaland.org/posts/2024-08-06_zkp/</link><description>&lt;p&gt;Today I learned about using zero-knowledge proofs in the context of passwords. These are my rough-and-ready notes from reading. Apparently OpenSSL has an implementation of the SRP algorithm.&lt;/p&gt;
&lt;h2 id="math-based-zkp-example"&gt;
  Math-based ZKP example
  &lt;a class="anchor" href="#math-based-zkp-example"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;Source for this example comes from &lt;a href="https://en.wikipedia.org/wiki/Zero-knowledge_proof#Discrete_log_of_a_given_value"&gt;Wikipedia&lt;/a&gt;. It might be good to read that in tandem with these notes.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;In this example Peggy is the person who wishes to &lt;em&gt;prove&lt;/em&gt; knowledge about something to Victor, the &lt;em&gt;verifier&lt;/em&gt;. Peggy is proving that she knows some value 


&lt;span&gt;
  \(x\)
&lt;/span&gt;
, but she doesn&amp;rsquo;t want to reveal the value of &lt;span&gt;
  \(x\)
&lt;/span&gt;
.&lt;/p&gt;
&lt;p&gt;Peggy and Victor need to share a large prime &lt;span&gt;
  \(p\)
&lt;/span&gt;
 and a generator &lt;span&gt;
  \(g\)
&lt;/span&gt;
. (This means that &lt;span&gt;
  \(g\)
&lt;/span&gt;
 and &lt;span&gt;
  \(p\)
&lt;/span&gt;
 must be relatively prime.)&lt;/p&gt;
&lt;p&gt;Peggy computes &lt;span&gt;
  \(g^x \mod{p} = y\)
&lt;/span&gt;
 and sends &lt;span&gt;
  \(y\)
&lt;/span&gt;
 to Victor.&lt;/p&gt;
&lt;p&gt;Peggy generates a random number &lt;span&gt;
  \(r\)
&lt;/span&gt;
 and computes &lt;span&gt;
  \(C = g^r \mod{p}\)
&lt;/span&gt;
 and sends &lt;span&gt;
  \(C\)
&lt;/span&gt;
 to Victor.&lt;/p&gt;
&lt;p&gt;Victor randomly issues one of two challenges:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Victor asks for &lt;span&gt;
  \(r\)
&lt;/span&gt;
. Peggy sends him &lt;span&gt;
  \(r\)
&lt;/span&gt;
 and Victor verifies that &lt;span&gt;
  \(C\)
&lt;/span&gt;
 matches &lt;span&gt;
  \(g^r \mod{p}\)
&lt;/span&gt;
.&lt;/li&gt;
&lt;li&gt;Victor asks for &lt;span&gt;
  \(s = (x &amp;#43; r) \mod{(p-1)}\)
&lt;/span&gt;
. Peggy computes this and sends the result to Victor. Victor checks that &lt;span&gt;
  \(g^s \equiv (C \cdot y) \mod{p}\)
&lt;/span&gt;
.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Repeat process &lt;span&gt;
  \(n\)
&lt;/span&gt;
 times to drive the probability that Peggy was just guessing to &lt;span&gt;
  \(\frac{1}{2^n}\)
&lt;/span&gt;
.&lt;/p&gt;
&lt;p&gt;The Wikipedia article has a good explanation for how an attacker could not mimic knowing &lt;span&gt;
  \(x\)
&lt;/span&gt;
 with this interactive proof.&lt;/p&gt;
&lt;h3 id="digression-properties-of-exponents-modulo-a-prime"&gt;
  Digression: properties of exponents modulo a prime
  &lt;a class="anchor" href="#digression-properties-of-exponents-modulo-a-prime"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;The last step works because&lt;/p&gt;
&lt;span&gt;
  \(\begin{aligned}
C \cdot y &amp;amp;\equiv g^r \cdot g^x &amp;amp;\mod{p} \\
          &amp;amp;\equiv g^{r &amp;#43; x \mod{p-1}} &amp;amp;\mod{p}
\end{aligned}\)
&lt;/span&gt;

&lt;p&gt;When working &lt;span&gt;
  \(\mod{p}\)
&lt;/span&gt;
, operations on combining exponents are &lt;span&gt;
  \(\mod{p-1}\)
&lt;/span&gt;
. This is a consequence of Fermat&amp;rsquo;s Little Theorem. Proof:&lt;/p&gt;
&lt;span&gt;
  \(a^e = a^{p-1} \cdot a^{p-1} \cdot a^{p-1} \cdots a^n\)
&lt;/span&gt;

&lt;p&gt;Where &lt;span&gt;
  \(a^{p-1}\cdot a^{p-1} \cdots a^{p-1} \equiv 1 \mod{p}\)
&lt;/span&gt;
 by Fermat&amp;rsquo;s theorem, and &lt;span&gt;
  \(n &amp;lt; p\)
&lt;/span&gt;
 and &lt;span&gt;
  \(e = m(p-1) &amp;#43; n\)
&lt;/span&gt;
 by the division algorithm. Therefore,
&lt;span&gt;
  \(n \equiv e \pmod{p-1}\)
&lt;/span&gt;
.&lt;/p&gt;
&lt;h2 id="zkps-used-for-password-based-authentication"&gt;
  ZKPs used for password-based authentication
  &lt;a class="anchor" href="#zkps-used-for-password-based-authentication"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;The above framework is not useful as-is for password authentication.&lt;/p&gt;
&lt;p&gt;There is a method for verifying that a user knows a password without revealing the password to the server. The standard is called &amp;ldquo;SRP&amp;rdquo; (Secure Remote Password) and there&amp;rsquo;s at least a version 6. As far as I can tell, version 6 is the most up-to-date version as of writing.&lt;/p&gt;
&lt;p&gt;Resources:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Secure_Remote_Password_protocol"&gt;SRP Protocol Wikipedia article&lt;/a&gt; has a good explaination.&lt;/p&gt;
&lt;div class="marginnote"&gt;
&lt;p&gt;To convert between PostScript format (&lt;code&gt;.ps&lt;/code&gt;) and PDF, run &lt;code&gt;ps2pdf srp6.ps&lt;/code&gt;. On macOS I got the &lt;code&gt;ps2pdf&lt;/code&gt; program by installing the &lt;code&gt;ghostscript&lt;/code&gt; package via Homebrew.&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="http://srp.stanford.edu/design.html"&gt;SRP protocol design document&lt;/a&gt;; includes links to a paper that I followed. I can&amp;rsquo;t find this paper in any official publication registry. URL is: &lt;a href="http://srp.stanford.edu/srp6.ps"&gt;http://srp.stanford.edu/srp6.ps&lt;/a&gt; and the title is: &amp;ldquo;SRP-6: Improvements and Refinements to the Secure Remote Password Protocol&amp;rdquo;. Note: it comes in PostScript format, so you&amp;rsquo;ll likely want to convert it to PDF to read it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://crypto.stackexchange.com/questions/25338/why-arent-zero-knowledge-proofs-used-in-practice-for-authentication"&gt;SE post: &amp;ldquo;Why aren&amp;rsquo;t ZKPs used in practice for authentication?&amp;rdquo;&lt;/a&gt;, top answer is excellent.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="running-srp-6a"&gt;
  Running SRP-6a
  &lt;a class="anchor" href="#running-srp-6a"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Shared: large safe prime &lt;span&gt;
  \(N\)
&lt;/span&gt;
 (suggested that &lt;span&gt;
  \(N = 2 * p &amp;#43; 1\)
&lt;/span&gt;
 where &lt;span&gt;
  \(p\)
&lt;/span&gt;
 is prime) and primitive root &lt;span&gt;
  \(g\)
&lt;/span&gt;
. (I.e., &lt;span&gt;
  \(N\)
&lt;/span&gt;
 and &lt;span&gt;
  \(g\)
&lt;/span&gt;
 must be relatively prime.)&lt;/p&gt;
&lt;p&gt;In this algorithm, the values &lt;span&gt;
  \(a\)
&lt;/span&gt;
 and &lt;span&gt;
  \(b\)
&lt;/span&gt;
 will be randomly generated. At the end, both parties will have a secret key &lt;span&gt;
  \(K\)
&lt;/span&gt;
 that they share.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client sends identifier &lt;span&gt;
  \(I\)
&lt;/span&gt;
 to Server.&lt;/li&gt;
&lt;li&gt;Server looks up the salt and the verification token &lt;span&gt;
  \((s,v)\)
&lt;/span&gt;
 associated with &lt;span&gt;
  \(I\)
&lt;/span&gt;
 and sends just &lt;span&gt;
  \(s\)
&lt;/span&gt;
 to Client.&lt;/li&gt;
&lt;li&gt;Client computes hash of salt, ID, and password &lt;span&gt;
  \(x = H(s, I, P)\)
&lt;/span&gt;
.&lt;/li&gt;
&lt;li&gt;Client generates a random value &lt;span&gt;
  \(a\)
&lt;/span&gt;
 and computes &lt;span&gt;
  \(A = g^a\)
&lt;/span&gt;
. Client sends &lt;span&gt;
  \(A\)
&lt;/span&gt;
 to Server.&lt;/li&gt;
&lt;li&gt;Server and client compute &lt;span&gt;
  \(k = H(N, g)\)
&lt;/span&gt;
. This is an enhancement from the older SRP-6 algorithm.&lt;/li&gt;
&lt;li&gt;Server generates a random value &lt;span&gt;
  \(b\)
&lt;/span&gt;
 and computes &lt;span&gt;
  \(B = kv &amp;#43; g^b\)
&lt;/span&gt;
. Server sends &lt;span&gt;
  \(B\)
&lt;/span&gt;
 to client.&lt;/li&gt;
&lt;li&gt;Server and Client both compute &lt;span&gt;
  \(u = H(A,B)\)
&lt;/span&gt;
. This is called the &lt;em&gt;scrambler&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now both parties have access to &lt;span&gt;
  \(A, B, k, u\)
&lt;/span&gt;
 and &lt;span&gt;
  \(g, N\)
&lt;/span&gt;
 of course. With this they can each create a shared session key:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;Client computation&lt;/dt&gt;
&lt;dd&gt;&lt;span&gt;
  \(K = (B-kg^x) ^ {(a &amp;#43; ux)}\)
&lt;/span&gt;
&lt;/dd&gt;
&lt;dt&gt;Server computation&lt;/dt&gt;
&lt;dd&gt;&lt;span&gt;
  \(K = (Av^u)^b\)
&lt;/span&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;The server and client must now both verify that they have the same value &lt;span&gt;
  \(K\)
&lt;/span&gt;
. One simple way to do this is to hash &lt;span&gt;
  \(K\)
&lt;/span&gt;
 (potentially with some other salting information like &lt;span&gt;
  \(A\)
&lt;/span&gt;
 and &lt;span&gt;
  \(B\)
&lt;/span&gt;
) and transmit that. Example from the paper:&lt;/p&gt;
&lt;span&gt;
  \( \begin{aligned}
 M_1 &amp;amp;= H(A,B,K) \\
 M_2 &amp;amp;= H(A,M_1,K)
\end{aligned}\)
&lt;/span&gt;

&lt;p&gt;The Client computes &lt;span&gt;
  \(M_1\)
&lt;/span&gt;
 and sends it to the server. The server should have enough information to recompute this value. Once that&amp;rsquo;s done, the server can compute &lt;span&gt;
  \(M_2\)
&lt;/span&gt;
 and send that back to the Client. (This last step is optional.) Now both parties know that they&amp;rsquo;ve got the right key. Use &lt;span&gt;
  \(K\)
&lt;/span&gt;
 as the session token.&lt;/p&gt;
&lt;p&gt;The Wikipedia article mentions that it is important that the client send its proof of &lt;span&gt;
  \(K\)
&lt;/span&gt;
 (i.e., the proof is the value &lt;span&gt;
  \(M_1\)
&lt;/span&gt;
) &lt;em&gt;first&lt;/em&gt;, and that the server should &lt;em&gt;not&lt;/em&gt; reply with &lt;span&gt;
  \(M_2\)
&lt;/span&gt;
 if verification fails.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what the communication flow would look like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Client &lt;span&gt;
  \(I\)
&lt;/span&gt;
 → Server&lt;/li&gt;
&lt;li&gt;Server &lt;span&gt;
  \(g,N,s,B\)
&lt;/span&gt;
 → Client&lt;/li&gt;
&lt;li&gt;Client &lt;span&gt;
  \(A, M_1\)
&lt;/span&gt;
 → Server&lt;/li&gt;
&lt;li&gt;Server &lt;span&gt;
  \(M_2\)
&lt;/span&gt;
 → Client&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now the Server and Client can communicate using secret key &lt;span&gt;
  \(K\)
&lt;/span&gt;
, which was only granted to the Client because it had the password that corresponded to the stored verifier &lt;span&gt;
  \(v\)
&lt;/span&gt;
 on the server.&lt;/p&gt;</description><author>Ashton Wiersdorf on Lambda Land</author><pubDate>Tue, 06 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://lambdaland.org/posts/2024-08-06_zkp/</guid></item><item><title>“No Salt”</title><link>https://jakeseliger.com/2024/08/05/no-salt/</link><description>This is by my brother, Sam. I arrived to Arizona late Saturday, after learning that my brother has only a few days left before cancer ends him. Jake’s wife, Bess, confessed that she had neither the willpower or the energy to take care of the post-death rituals—in this case, cremation, followed by a celebration of [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Tue, 06 Aug 2024 01:23:22 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/08/05/no-salt/</guid></item><item><title>How to update multiple records with different values in Ecto.Repo.update_all</title><link>https://geekmonkey.org/updating-multiple-records-with-different-values-in-ecto-repo-update_all/</link><description>&lt;p&gt;I recently ran into a problem where I wanted to update many database records with a single &lt;code&gt;update_all/2&lt;/code&gt;. &lt;/p&gt;&lt;p&gt;In this particular case, I was dealing with a table that leveraged &lt;code&gt;parent_id&lt;/code&gt; to model a basic parent-child hierarchy. &lt;/p&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;parent_id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;NULL&lt;/td&gt;
&lt;td&gt;root&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;child&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;</description><author>geekmonkey</author><pubDate>Mon, 05 Aug 2024 23:32:50 GMT</pubDate><guid isPermaLink="true">https://geekmonkey.org/updating-multiple-records-with-different-values-in-ecto-repo-update_all/</guid></item><item><title>Software at Scale 60 - Data Platforms with Aravind Suresh</title><link>https://www.softwareatscale.dev/p/software-at-scale-60-data-platforms</link><description>Aravind was a Staff Software Engineer at Uber, and currently works at OpenAI.</description><author>Software at Scale</author><pubDate>Mon, 05 Aug 2024 20:58:51 GMT</pubDate><guid isPermaLink="true">https://www.softwareatscale.dev/p/software-at-scale-60-data-platforms</guid></item><item><title>The crazy engineering of Venice</title><link>https://nicolaiarocci.com/the-crazy-engineering-of-venice/</link><description>&lt;p&gt;We spent a weekend in Venice&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt; a short while ago, and one of the things that caught my attention was the wells in the city&amp;rsquo;s squares. Is there fresh water underneath that brackish swamp water? Well, no. The water from the wells in Venice is rainwater, collected by an ingenious hydraulic collection system that leveraged the square and surrounding buildings.&lt;/p&gt;
&lt;p&gt;I learned this and other intriguing tidbits by watching &lt;a href="https://youtu.be/77omYd0JOeA"&gt;The Crazy Engineering of Venice&lt;/a&gt; on YouTube.&lt;/p&gt;</description><author>Nicola Iarocci</author><pubDate>Mon, 05 Aug 2024 17:45:00 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/the-crazy-engineering-of-venice/</guid></item><item><title>Capability makes you life simpler</title><link>https://nicolaiarocci.com/capability-makes-you-life-simpler/</link><description>&lt;p&gt;Quoting &lt;a href="https://www.bryanbraun.com/2024/07/31/capability-makes-your-life-simpler/"&gt;Bryan Baun&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Capability makes your life simpler. Tolerance, skills, knowledge, and health are always with you, wherever you go. They are assets but they take up no space. They are stored in your body. Some lack capability through no fault of their own, but anyone can increase their capability. It’s an investment that pays dividends every day.&lt;/p&gt;&lt;/blockquote&gt;</description><author>Nicola Iarocci</author><pubDate>Mon, 05 Aug 2024 12:59:37 GMT</pubDate><guid isPermaLink="true">https://nicolaiarocci.com/capability-makes-you-life-simpler/</guid></item><item><title>Starting hospice. The end</title><link>https://jakeseliger.com/2024/08/04/starting-hospice-the-end/</link><description>I’m entering hospice. It’s time, and realistically past time. The squamous cell carcinoma tumors are growing, and the two doses of spot radiation I got on June 10 and 12 have utterly destroyed whatever quality of life I had. This weekend, a nurse came by and did some planning with Bess and me. Our extensive [&amp;#8230;]</description><author>The Story's Story</author><pubDate>Mon, 05 Aug 2024 03:47:49 GMT</pubDate><guid isPermaLink="true">https://jakeseliger.com/2024/08/04/starting-hospice-the-end/</guid></item><item><title>Left-handed Cursor on MacOS</title><link>https://june.kim/left-handed-cursor-on-macos/</link><author>june.kim</author><pubDate>Mon, 05 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://june.kim/left-handed-cursor-on-macos/</guid></item><item><title>💡 The Pivot</title><link>https://james-carr.org/posts/2024-07-29-the-pivot/</link><description>I just got back from a month-long break in Cambodia, traveling with my family. I took advantage of the wide-open space with zero work expectations to give me some time to truly reflect on my career so far and what comes next. I&amp;rsquo;ve had a pretty successful career in tech spanning twenty years that, for the most part, paid off quite well. It&amp;rsquo;s afforded me a life that I never would have imagined when I was running around barefoot in a trailer park in Warrenton, Missouri, and for that, I&amp;rsquo;ll always be immensely thankful.</description><author>James Carr</author><pubDate>Mon, 05 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://james-carr.org/posts/2024-07-29-the-pivot/</guid></item><item><title>A cloud engineer's first QuestDB Pull Request</title><link>https://sklar.rocks/my-first-questdb-pr/</link><description>&lt;p&gt;Originally published on the &lt;a href="https://questdb.io/blog/my-first-questdb-pull-request/" rel="external"&gt;QuestDB Blog&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;It was a little over a year and a half into my tenure as a cloud engineer at QuestDB
when I started my first Pull Request to the core database. Before that, I had spent
my time working with tools like Kubernetes and Docker to manage QuestDB deployments
across multiple datacenters. I implemented production-grade observability solutions,
wrote a Kubernetes operator in Golang, and pored over seemingly minute details our AWS bills.&lt;/p&gt;
&lt;p&gt;While I enjoyed the cloud-native work (and still do!), I continued to have a nagging desire
to meaningfully contribute the actual database that I spent all day orchestrating. It took the
birth of my daughter, and the accompanying parental leave, for me to disconnect a bit and
think about my priorities and career goals.&lt;/p&gt;
&lt;p&gt;What was really stopping me from contributing?
Was it the dreaded imposter syndrome? Or just a backlog of cloud-related tasks on my plate? After all,
QuestDB is open source, so not much was stopping me from submitting some code changes.&lt;/p&gt;
&lt;p&gt;With this mindset, I had a meeting with Vlad, our CTO, as I was ending my leave and about to start
ramping my workload back up.&lt;/p&gt;
&lt;p&gt;&lt;img alt="First PR to QuestDB Core" src="/img/blog/my-first-questdb-pr/first-pr-to-core-db.webp" /&gt;&lt;/p&gt;
&lt;h2 id="config-hot-reloading"&gt;Config Hot-Reloading&lt;/h2&gt;
&lt;p&gt;Since I was coming back to work part-time for a bit, I figured that I could pick up a project that wasn't particularly time-sensitive so I could continue to help out with the
baby at home.&lt;/p&gt;
&lt;p&gt;One item that came up was the ability for QuestDB to adjust its runtime
configuration on-the-fly. To do so, we'd need to monitor the config file,
server.conf, and apply any configuration changes to the database without restarting it.&lt;/p&gt;
&lt;p&gt;This task immediately resonated with me, since I've personally felt the pain of not having this "hot-reload" feature. I've spent way too many hours writing Kubernetes operator code that restarts a running QuestDB Pod on a mounted ConfigMap change. Having the opportunity to build a hot-reload feature was tantalizing, to say the least.&lt;/p&gt;
&lt;p&gt;So I was off to the races, excited to get started working on my first major contribution to QuestDB.&lt;/p&gt;
&lt;p&gt;We quickly arrived at a basic design.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Build a new FileWatcher class: Monitor the database's server.conf file for changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Detect changes: FileWatcher detects changes to the server.conf file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Load new values: Read the server.conf and load any new configuration values from the updated file.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Validate the updated configuration: Validate the new server configuration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Apply the new configuration: Apply the new configuration values to the running server without restarting it.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Seems easy enough!&lt;/p&gt;
&lt;p&gt;But like in most production-grade code, this seemingly simple problem got quite
complex very quickly...&lt;/p&gt;
&lt;h2 id="complications"&gt;Complications&lt;/h2&gt;
&lt;p&gt;It's one thing to add a new feature to a relatively greenfield codebase. But
it's quite another to add one to a mature codebase with over 100 contributors
and years of history. Not all of these challenges were evident at the start, but
over time, I started to internalize them.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;FileWatcher&lt;/code&gt; component needed to be cross-platform, since QuestDB
supports Linux, macOS, and Windows.&lt;/li&gt;
&lt;li&gt;The new reloading server config (called &lt;code&gt;DynamicServerConfig&lt;/code&gt;) had to slot-in
seamlessly to the existing plumbing that runs QuestDB and allows
&lt;a href="https://questdb.io/enterprise/" rel="external"&gt;QuestDB Enterprise&lt;/a&gt; to plug in to the open core.&lt;/li&gt;
&lt;li&gt;We wanted the experience to be as seamless as possible for end users. This
means that we couldn't forcibly close open database connections or restart
the entire server.&lt;/li&gt;
&lt;li&gt;Caching configuration values was much more common throughout the codebase
than we initially thought. Many classes and factories read the server
configuration only once on initialization, and would need to be
re-initialized to accommodate a new config setting.&lt;/li&gt;
&lt;li&gt;Like everything we do at QuestDB, performance is paramount. The solution
needed to be as efficient as possible to allocate compute resources to more
important things, like ingesting and querying data.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="inotify-kqueue-epoll-oh-my"&gt;Inotify, kqueue, epoll, oh my!&lt;/h2&gt;
&lt;p&gt;Nine times out of ten, if you ask me to write a cross-platform file watcher
library, I would google for "cross-platform filewatcher in Java". But working on
a codebase that values strict memory accounting and efficient resource usage, it
just didn't feel right to pull in a 3rd party library off the shelf. To maintain
the performance that QuestDB is known for, it's crucial to understand what every
bit of code is doing under the hood. So, in spite of the famous "Not Invented
Here Syndrome", I went about learning how to implement file watchers at the
syscall level in C.&lt;/p&gt;
&lt;p&gt;I've felt this way a few times in my career, picking up something so brand new
that I wasn't even sure where to start. And during these times, I've reached for
venerable and canonical books on the subject to learn the basics. So, I hit up
Amazon and got some reading material.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Reading Material" src="/img/blog/my-first-questdb-pr/books.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Since I'd recently been coding on my fancy new Ryzen Zen 4-based EndeavourOS
desktop, I decided to start with the Linux implementation. I began writing some
primitive C code, working with APIs like
&lt;a href="https://man7.org/linux/man-pages/man7/inotify.7.html" rel="external"&gt;inotify &lt;/a&gt; and
&lt;a href="https://man7.org/linux/man-pages/man7/epoll.7.html" rel="external"&gt;epoll&lt;/a&gt;, to effectively park
a thread and wait for a change in a specific file or directory. Once a change
was detected by one of these lower-level libraries, execution would continue
where I would perform some basic filtering for a particular filename and return.&lt;/p&gt;
&lt;p&gt;Once I was happy with my implementation, I still needed to make the code
available to the JVM, where QuestDB runs. I was able to use the
&lt;a href="https://en.wikipedia.org/wiki/Java_Native_Interface" rel="external"&gt;Java Native Interface (JNI)&lt;/a&gt;
to wrap my functions in macros that allow the JVM to load the compiled binary
and call them directly from Java.&lt;/p&gt;
&lt;p&gt;But this was only the start. I also needed to make this work for macOS and
Windows. Unfortunately, &lt;code&gt;inotify&lt;/code&gt; isn't included in either of those operating
systems, so I needed to find an alternative. Since macOS is built on top of
FreeBSD, they share many of the same core libraries. This includes
&lt;a href="https://man.freebsd.org/cgi/man.cgi?kqueue" rel="external"&gt;kqueue&lt;/a&gt;, which I was able to use
instead of &lt;code&gt;inotify&lt;/code&gt; to implement the core functionality of my filewatcher.
Luckily, QuestDB already has some &lt;code&gt;kqueue&lt;/code&gt; code written, since we use it to
handle network traffic on those platforms. So I only had to add a few new
functions in C to add the functionality that I required.&lt;/p&gt;
&lt;p&gt;As for Windows? Vlad was a lifesaver there, since I don't have a Windows
machine! He used low-level WinAPI libraries to implement the filewatcher and
made them available to QuestDB through the JNI.&lt;/p&gt;
&lt;h2 id="navigating-plumbing-like-mario-and-luigi"&gt;Navigating plumbing like Mario and Luigi&lt;/h2&gt;
&lt;p&gt;When I first started reading the QuestDB codebase, I found a web of classes and
interfaces with abstract names like &lt;code&gt;FactoryProviderFactory&lt;/code&gt; and
&lt;code&gt;PropBootstrapConfiguration&lt;/code&gt;. Was this that "enterprise Java"-style of
programming that I've heard so much about?&lt;/p&gt;
&lt;p&gt;&lt;img alt="FizzBuzzEnterpriseEdition" src="/img/blog/my-first-questdb-pr/enterprise-style-java.webp" /&gt;&lt;/p&gt;
&lt;p&gt;After a lot of &lt;code&gt;F12&lt;/code&gt; and &lt;code&gt;Opt&lt;/code&gt;+&lt;code&gt;Shift&lt;/code&gt;+&lt;code&gt;F12&lt;/code&gt; in IntelliJ, I started to build a
mental map of the project structure and things started to make more sense. At
its core, the entrypoint is a linear process. We use Java's built-in
&lt;a href="https://docs.oracle.com/javase/8/docs/api/java/util/Properties.html" rel="external"&gt;Properties&lt;/a&gt;
to read &lt;code&gt;server.conf&lt;/code&gt; into a property of a &lt;code&gt;BootstrapConfiguration&lt;/code&gt;, pass that
in a constructor to a &lt;code&gt;Bootstrap&lt;/code&gt; class, and use that as an input to
&lt;code&gt;ServerMain&lt;/code&gt;, QuestDB's entrypoint.&lt;/p&gt;
&lt;p&gt;&lt;img alt="QuestDB Bootstrap Flow" src="/img/blog/my-first-questdb-pr/mermaid-diagram-2024-08-05-152029.webp" /&gt;&lt;/p&gt;
&lt;p&gt;The reason for so many factories, interfaces, and abstract classes is twofold.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;it allows devs to mock just about any dependency in unit tests&lt;/li&gt;
&lt;li&gt;it creates abstraction layers for QuestDB Enterprise to use and extend
existing core components&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now, I was ready to make some changes! I added a new
&lt;code&gt;DynamicServerConfiguration&lt;/code&gt; interface that exposed a &lt;code&gt;reload()&lt;/code&gt; method, and
created an implementation of this class that used the delegate pattern to wrap a
legacy &lt;code&gt;ServerConfiguration&lt;/code&gt; interface. When &lt;code&gt;reload()&lt;/code&gt; was called, we would
read the &lt;code&gt;server.conf&lt;/code&gt; file, validate it, and atomically swap the delegate
config with the new version. I then created an instance of my FileWatcher in the
main QuestDB entrypoint with a callback that called
&lt;code&gt;DynamicServerConfiguration.reload()&lt;/code&gt; when it was triggered (on a file change).&lt;/p&gt;
&lt;p&gt;&lt;img alt="Dynamic Server Config Reload Sequence Diagram" src="/img/blog/my-first-questdb-pr/mermaid-diagram-2024-08-05-152042.webp" /&gt;&lt;/p&gt;
&lt;p&gt;As you can imagine, wiring this all up wasn't the easiest, since I needed to
maintain the existing class initialization order so that all dependencies would
be ready at the correct time. I also didn't want to significantly modify the
entrypoint of QuestDB. I felt this would not only confuse developers, but also
cause problems when trying to compile Enterprise Edition.&lt;/p&gt;
&lt;p&gt;Vlad had some great advice for me here, paraphrasing, "Make a change and re-run
unit tests. If you've broken 100s, then try a different way. If you've only
broken around 5, then you're on the right track."&lt;/p&gt;
&lt;h2 id="now-what-can-we-actually-reload"&gt;Now, what can we actually reload?&lt;/h2&gt;
&lt;p&gt;There are a lot of possible settings to change in QuestDB, at all different
levels of the database. At first, we thought that something like hot-reloading a
query timeout would be a nice feature to have. This way, if I find that a
specific query is taking a long to execute, I can simply modify my &lt;code&gt;server.conf&lt;/code&gt;
without having to restart the database.&lt;/p&gt;
&lt;p&gt;Unfortunately, query timeouts are cached deep inside the cairo query engine, and
updating those components to read directly from the &lt;code&gt;DynamicServerConfiguration&lt;/code&gt;
would be an exercise in futility.&lt;/p&gt;
&lt;p&gt;After a lot of poking and prodding of the codebase, we found something that
would work, pgwire credentials! QuestDB supports configurable read/write and
readonly users that are used to secure communication with the database over
Postgres wire protocol. We validate these users' credentials with a class that
reads them from a &lt;code&gt;ServerConfiguration&lt;/code&gt; and stores them in a custom (optimized)
utf8 sink.&lt;/p&gt;
&lt;p&gt;I was able to modify this class to accept my new dynamic configuration, cache
it, and check whether the config reference has changed from the previous call.
Because the dynamic configuration uses the delegate pattern, after a successful
configuration reload (where we re-initialize the delegate), a new configuration
would have a different memory address, and the cached reference would not match.
At this point, the class would know to update its username &amp;amp; password sinks with
the newly-updated config values.&lt;/p&gt;
&lt;h2 id="the-big-moment-ready-to-merge"&gt;The big moment, ready to merge!&lt;/h2&gt;
&lt;p&gt;It takes a village to raise a child. I've learned that already in my short time
as a father. And a Pull Request is no different. Both Vlad and
&lt;a href="https://github.com/jerrinot" rel="external"&gt;Jaromir&lt;/a&gt; helped to get this thing over the finish
line. From acting as a soundboard to getting their hands dirty in Java and C
code, they really provided fantastic support over the 5 months that my PR was
open.&lt;/p&gt;
&lt;p&gt;Towards the end of the project, even though all tests were passing in core,
there was even a wrinkle in QuestDB Enterprise that prevented us from merging
the PR. We realized that our abstraction layers were not quite perfect, so we
couldn't reuse some parts of the core codebase in Enterprise. Instead of
re-architecting everything from scratch and probably adding weeks or more to the
project, we ended up just copying a few lines from core into Enterprise. It
compiled, tests passed, and everyone was happy.&lt;/p&gt;
&lt;p&gt;&lt;img alt="PR merged on GitHub" src="/img/blog/my-first-questdb-pr/merged-pr.webp" /&gt;&lt;/p&gt;
&lt;p&gt;Now that Enterprise and core were both ready to go with a green check mark on GitHub, I hit the "Merge" button on GitHub and went outside for a long walk.&lt;/p&gt;
&lt;p&gt;Now that Enterprise and core were both ready to go with a green check mark on
GitHub, I hit the "Merge" button on GitHub and went outside for a long walk.&lt;/p&gt;
&lt;h2 id="learnings"&gt;Learnings&lt;/h2&gt;
&lt;p&gt;While this ended up being an incredibly long journey to "simply" let users
change pgwire credentials on-the-fly, I consider it to be a massive personal
success in my growth and development as a software engineer. The amount of
confidence that this task has given me cannot be understated. From this project
alone, I've:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;written my own C code for the first time&lt;/li&gt;
&lt;li&gt;learned several new kernel APIs&lt;/li&gt;
&lt;li&gt;used &lt;a href="https://www.baeldung.com/java-unsafe" rel="external"&gt;unsafe semantics&lt;/a&gt; in a
memory-managed programming language&lt;/li&gt;
&lt;li&gt;navigated the inner workings of a massive, mature codebase&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And all in a new IDE for me (IntelliJ)!&lt;/p&gt;
&lt;p&gt;With confidence stemming from the breadth and depth of work in this project, I'm
ready to take on my next challenge in the core QuestDB codebase. I've already
implemented a few simple SQL functions and started to grok the SQL Expression
Parser. But given our
&lt;a href="https://github.com/orgs/questdb/projects/1/views/5" rel="external"&gt;aggressive roadmap&lt;/a&gt; with
features like
&lt;a href="https://github.com/orgs/questdb/projects/1/views/5?pane=issue&amp;amp;itemId=52061180" rel="external"&gt;Parquet support&lt;/a&gt;,
&lt;a href="https://github.com/orgs/questdb/projects/1/views/5?pane=issue&amp;amp;itemId=71101196" rel="external"&gt;Array data types&lt;/a&gt;,
and an
&lt;a href="https://github.com/orgs/questdb/projects/1/views/5?pane=issue&amp;amp;itemId=38082772" rel="external"&gt;Apache Arrow ADBC driver&lt;/a&gt;,
I'm sure that there are plenty of other things for me to contribute in the
future! What's even more exciting is that I can use my cloud-native expertise to help
drive the database forward as we move towards a fully distributed architecture.&lt;/p&gt;
&lt;p&gt;If you're curious about all of this work, here's a
&lt;a href="https://github.com/questdb/questdb/pull/4168" rel="external"&gt;link to the PR&lt;/a&gt;&lt;/p&gt;</description><author>Steven Sklar | My Blog</author><pubDate>Mon, 05 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://sklar.rocks/my-first-questdb-pr/</guid></item><item><title>First impressions of Gleam: lots of joys and some rough edges</title><link>https://ntietz.com/blog/first-impressions-of-gleam/?utm_source=atom&amp;utm_medium=feed</link><description>&lt;p&gt;My friend Erika is a big fan of Gleam, and her enthusiasm (and explicit encouragement) finally got me to learn the language.
It's a functional programming language which targets both the &lt;a href="https://en.wikipedia.org/wiki/BEAM_%28Erlang_virtual_machine%29"&gt;BEAM&lt;/a&gt; (Erlang's VM) and JavaScript.
This makes it appealing as a language that can target both frontend and backend applications easily, can benefit from the large Erlang/Elixir and JavaScript ecosystems, and lets you use Erlang's fantastic scalability resiliency.&lt;/p&gt;
&lt;p&gt;I've not used it in a real-world context yet (nor am I sure I'll ever have the opportunity), but going through the &lt;a href="https://tour.gleam.run/"&gt;language tour&lt;/a&gt; gave me a &lt;em&gt;lot&lt;/em&gt; of appreciation for Gleam.
After going through it, I've got a list of things I definitely want to copy in the language I'm working on, Lilac—and a short list of things I do &lt;em&gt;not&lt;/em&gt; want to repeat from Gleam (as a preference).&lt;/p&gt;
&lt;h1 id="overall-first-experience"&gt;Overall first experience&lt;/h1&gt;
&lt;p&gt;Getting started with Gleam was a pretty good experience.
The first thing did was install it locally, but you don't have to do that.
The language tour itself runs Gleam in the browser, so you can learn the language without ever installing it locally!&lt;/p&gt;
&lt;p&gt;I did install it locally for two reasons: I wanted to learn it in my usual programming environment with an LSP available; and I knew I'd need it installed to collaborate on a small Gleam project with Erika.
Compared to Rust, it was a little bit harder to get installed, since you need at least three distinct toolchains (the &lt;code&gt;gleam&lt;/code&gt; binary, Erlang/Elixir packages, and rebar3), but this was pretty well documented.
It was just a small source of friction, but nothing out of the ordinary.&lt;/p&gt;
&lt;p&gt;That last bit captures a lot of my experience with Gleam, to be honest.
There are a number of things that have bits of friction or are surprising or not what I'd expect, but there's &lt;a href="https://gleam.run/documentation/"&gt;good documentation&lt;/a&gt; which gets you past all the sticking points.
The docs are &lt;em&gt;shockingly good&lt;/em&gt; for the age and size of the project.
It was really easy to get started, and the language tour got me up and running with it far faster than I expected&lt;sup class="footnote-reference" id="fr-except-sick-1"&gt;&lt;a href="https://ntietz.com/blog/first-impressions-of-gleam/#fn-except-sick"&gt;[1]&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h1 id="the-joyous-parts"&gt;The joyous parts&lt;/h1&gt;
&lt;p&gt;There is a &lt;em&gt;lot&lt;/em&gt; to like in Gleam, and I'm looking forward to using it in some small collaborations.
It's not something I'm bringing to work, but there are a lot of parts of it that I'm going to definitely carry forward into languages I design.
And these are all things I'll look for in other languages I use, too.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The community is really welcoming and helpful.&lt;/strong&gt;
I joined the Gleam Discord well before I started writing Gleam to hang out with friends a bit, and they're so welcoming there!
It's a really lovely community of really helpful people.
You're not made to feel stupid for having questions, and you can chat directly with the people who make the language work.
This community is obviously shaped by the care and love that the language's creator put into the community from the outset.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The pattern matching is a case study in how to do it.&lt;/strong&gt;
Gleam's pattern matching is so good, and &lt;a href="https://tour.gleam.run/everything/#flow-control-case-expressions"&gt;well documented&lt;/a&gt; already.
I'll give just a few examples here to avoid repeating the docs at length.
A couple of the must-haves I really like are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Exhaustiveness checking.&lt;/em&gt;
If I do a pattern match and I'm missing a few values, the compiler will catch this!
This is very useful for custom data types where you may miss a possibility.&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;let message = case cores {
&lt;/span&gt;&lt;span&gt;  0 -&amp;gt; &amp;quot;how does your computer have no cores?&amp;quot;
&lt;/span&gt;&lt;span&gt;  1 -&amp;gt; &amp;quot;this is what, 1999?&amp;quot;
&lt;/span&gt;&lt;span&gt;  2 -&amp;gt; &amp;quot;now we're getting somewhere&amp;quot;
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
The compiler will complain about this because if &lt;code&gt;cores&lt;/code&gt; is anything other than 0, 1, or 2, it doesn't have a matching arm!&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Structural pattern matching.&lt;/em&gt;
You can pattern match on the contents of a string or a list or the structure of any data types you define.
Here's a small example defining the max of a list of integers.&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;pub fn listmax(xs) {
&lt;/span&gt;&lt;span&gt;  case xs {
&lt;/span&gt;&lt;span&gt;    [] -&amp;gt; 0
&lt;/span&gt;&lt;span&gt;    [x, y] -&amp;gt; int.max(x, y)
&lt;/span&gt;&lt;span&gt;    [x, ..rest] -&amp;gt; int.max(x, listmax(rest))
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
This lets you write really concise &lt;em&gt;and&lt;/em&gt; legible code.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Matching on multiple values.&lt;/em&gt;
This is pretty common when you can define tuples, but it's also something not to take for granted.
It's great to be able to match on multiple things with &lt;code&gt;case x, y { ... }&lt;/code&gt;.
If you're doing pattern matching, you gotta have this.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Shadowing makes immutability's ergonomics nice.&lt;/strong&gt;
This is something I like in Rust, and it's something I'm very happy to see in Gleam:
immutable variables, but you can get safe faux-mutability by using shadowing!
You can't even do something like &lt;code&gt;x = 10&lt;/code&gt; to reassign to something you've previously declared, you only have this style.&lt;/p&gt;
&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;let x = 10
&lt;/span&gt;&lt;span&gt;io.debug(x)
&lt;/span&gt;&lt;span&gt;let x = 15
&lt;/span&gt;&lt;span&gt;io.debug(x)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is particularly helpful for modifying existing data structures (updating a field, adding to a list or a map, etc.) because you won't introduce race conditions but you can still keep good ergonomics from shadowing.&lt;/p&gt;
&lt;p&gt;Note that I do prefer having the &lt;em&gt;option&lt;/em&gt; of mutability, though, so this isn't a pure joy for me.
Immutability feels a lot better when paired with shadowing, but there are some things that are a lot easier to express using mutation.
And, as we'll talk about later, not having mutation means you can't have a useful loop construct!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There's a good LSP out of the box.&lt;/strong&gt;
Gleam was started in 2019, and the LSP was introduced &lt;a href="https://gleam.run/news/v0.21-introducing-the-gleam-language-server/"&gt;in 2022&lt;/a&gt;.
This lets you get Gleam support in just about every editor you're likely to use!
Newer languages don't always have this, so it's great to see.
Using it while learning Gleam just emphasizes to me how important it is to get this early.
It helps &lt;em&gt;significantly&lt;/em&gt; with adoption of the language, because it's easier to learn a language when the tooling can help you with it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Qualified imports improve code discoverability.&lt;/strong&gt;
One of my &lt;em&gt;biggest&lt;/em&gt; problems reading Rust code is that when you import a trait, you can't tell at the call site where the code is coming from.
This is especially problematic in examples where imports may even be omitted, and you can't figure out where these methods came from.
But it goes even further, with individual types and functions: when they're imported freely into the local namespace, at a certain point it just becomes confusing where they're coming from.&lt;/p&gt;
&lt;p&gt;Gleam encourages qualified imports, and this &lt;em&gt;greatly&lt;/em&gt; aids in discoverability&lt;sup class="footnote-reference" id="fr-as-erika-says-1"&gt;&lt;a href="https://ntietz.com/blog/first-impressions-of-gleam/#fn-as-erika-says"&gt;[2]&lt;/a&gt;&lt;/sup&gt;, which ultimately aid reading and understanding new codebases.
While you &lt;em&gt;can&lt;/em&gt; do unqualified imports, making qualified imports idiomatic means that most code ends up a little easier to learn from, which greatly helps people pick up codebases and the language.&lt;/p&gt;
&lt;p&gt;Consider this (abridged) example from the &lt;a href="https://tour.gleam.run/everything/#standard-library-result-module"&gt;Gleam tour&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;import gleam/int
&lt;/span&gt;&lt;span&gt;import gleam/io
&lt;/span&gt;&lt;span&gt;import gleam/result
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;pub fn main() {
&lt;/span&gt;&lt;span&gt;  // skipping most of the example
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;  int.parse(&amp;quot;-1234&amp;quot;)
&lt;/span&gt;&lt;span&gt;  |&amp;gt; result.map(int.absolute_value)
&lt;/span&gt;&lt;span&gt;  |&amp;gt; result.try(int.remainder(_, 42))
&lt;/span&gt;&lt;span&gt;  |&amp;gt; io.debug
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Even if I omitted the imports, you'd know that the &lt;code&gt;map&lt;/code&gt; here is &lt;em&gt;probably not&lt;/em&gt; the &lt;code&gt;map&lt;/code&gt; function from the list module, and you'll know you need to understand it differently!
In contrast, if the imports were unqualified, you'd just have:&lt;/p&gt;
&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;parse(&amp;quot;-1234&amp;quot;)
&lt;/span&gt;&lt;span&gt;|&amp;gt; map(absolute_value)
&lt;/span&gt;&lt;span&gt;|&amp;gt; try(remainder(_, 42))
&lt;/span&gt;&lt;span&gt;|&amp;gt; debug
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And you'd be left with a lot more questions: parse what? which &lt;code&gt;map&lt;/code&gt;? where's &lt;code&gt;try&lt;/code&gt; from?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Labeled arguments clarify programmer intent.&lt;/strong&gt;
If you see this code, it's not very clear which number does what:&lt;/p&gt;
&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;float.power(2.0, 3.0)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Because we're taught exponents in school, you can probably guess that this is 2 to the power of 3, but most things won't be that clear, and you're &lt;em&gt;still guessing&lt;/em&gt;.
With labeled arguments, you can make your intent clear:&lt;/p&gt;
&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;float.power(2.0, of: 3.0)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Here you're raising 2 to the power of 3.
This is especially clarifying in pipelines, where one argument is omitted:&lt;/p&gt;
&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;2.0 |&amp;gt; power(of: 3.0)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The standard library is written in the language itself!&lt;/strong&gt;
This is &lt;em&gt;wonderful&lt;/em&gt; because it means that practitioners of the language can read the code and understand it, where a lot of the Python standard library (for example) is written in C and is far less accessible to your average Python programmer.
And it means that the language developers work on the language and standard library at the same time, so they get to feel the effects of any language change in a real Gleam codebase.&lt;/p&gt;
&lt;p&gt;The standard library itself is &lt;em&gt;also&lt;/em&gt; pretty nice.
It isn't that big yet, but it includes a lot of the things you'd want to see: options, results, lists.
Most of what you need for things like Advent of Code are included out of the box!&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;todo&lt;/code&gt; and &lt;code&gt;panic&lt;/code&gt; as keywords make a lot of sense.&lt;/strong&gt;
Most languages I've used don't have any built-in &lt;code&gt;todo&lt;/code&gt; affordance.
My preferred language that does, Rust, has it as a macro (&lt;code&gt;todo!&lt;/code&gt;), same with &lt;code&gt;panic!&lt;/code&gt;.
This is fine, but it feels good as a user of the language to have these as keywords.
It means that we know they're an intentional part of the &lt;em&gt;language design&lt;/em&gt; itself, and the compiler can do useful things with them.
In particular, Gleam's compiler will give you a warning whenever you compile and your code contains &lt;code&gt;todo&lt;/code&gt;, since that means it's not complete yet.&lt;/p&gt;
&lt;h1 id="the-rough-edges"&gt;The rough edges&lt;/h1&gt;
&lt;p&gt;Of course, no language is without its quirks and drawbacks.
I'm a big fan of Rust, and I have no shortage of things I don't like in it&lt;sup class="footnote-reference" id="fr-hello-lilac-1"&gt;&lt;a href="https://ntietz.com/blog/first-impressions-of-gleam/#fn-hello-lilac"&gt;[3]&lt;/a&gt;&lt;/sup&gt;.
Gleam is no exception in this.
I came away with quite a few things I am definitely not a fan of, where I won't want to replicate it elsewhere.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;There aren't loops!&lt;/strong&gt;
This is intentional, since you can do everything through recursion to create looping behavior.
It's also a necessity, given you don't have mutation: you can't really loop in most of the useful ways if you can't mutate a variable.
And some people will accurately point out that you will usually use higher-level functions like &lt;code&gt;list.map&lt;/code&gt; and &lt;code&gt;list.fold&lt;/code&gt; most of the time, anyway, rather than explicitly recursing!
This really falls flat for me when I look at a few examples, though.&lt;/p&gt;
&lt;p&gt;The first one I'll look at is from the Gleam tour itself: factorial.
The tour presents a nice, straightforward factorial implementation that is commonly used as an example when teaching recursion.
But then they continue on and modify this to use tail calls so that it can be optimized, so you don't blow the call stack.
We end up with this:&lt;/p&gt;
&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;pub fn factorial(x: Int) -&amp;gt; Int {
&lt;/span&gt;&lt;span&gt;  // The public function calls the private tail recursive function
&lt;/span&gt;&lt;span&gt;  factorial_loop(x, 1)
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;fn factorial_loop(x: Int, accumulator: Int) -&amp;gt; Int {
&lt;/span&gt;&lt;span&gt;  case x {
&lt;/span&gt;&lt;span&gt;    0 -&amp;gt; accumulator
&lt;/span&gt;&lt;span&gt;    1 -&amp;gt; accumulator
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    // The last thing this function does is call itself
&lt;/span&gt;&lt;span&gt;    // In the previous lesson the last thing it did was multiply two ints
&lt;/span&gt;&lt;span&gt;    _ -&amp;gt; factorial_loop(x - 1, accumulator * x)
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this example, we had to make a private function with a different interface to leverage tail call optimization (resulting in harder to read code), and that private function is &lt;em&gt;very&lt;/em&gt; hard to understand compared to the usual imperative loop-based solution:&lt;/p&gt;
&lt;pre class="language-rust " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-rust"&gt;&lt;span style="color: #fa5c4b;"&gt;fn &lt;/span&gt;&lt;span style="color: #8ec07c;"&gt;factorial&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span style="color: #fdf4c1;"&gt;x&lt;/span&gt;&lt;span&gt;: &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u64&lt;/span&gt;&lt;span&gt;) -&amp;gt; &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;u64 &lt;/span&gt;&lt;span&gt;{
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;let mut&lt;/span&gt;&lt;span&gt; product &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;= &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;1&lt;/span&gt;&lt;span&gt;;
&lt;/span&gt;&lt;span&gt;  &lt;/span&gt;&lt;span style="color: #fa5c4b;"&gt;for&lt;/span&gt;&lt;span&gt; i &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;in &lt;/span&gt;&lt;span style="color: #d3869b;"&gt;1&lt;/span&gt;&lt;span style="color: #fe8019;"&gt;..=&lt;/span&gt;&lt;span&gt;x {
&lt;/span&gt;&lt;span&gt;    product &lt;/span&gt;&lt;span style="color: #fe8019;"&gt;*=&lt;/span&gt;&lt;span&gt; i;
&lt;/span&gt;&lt;span&gt;  }
&lt;/span&gt;&lt;span&gt;  product
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Note that I'm not advocating for &lt;em&gt;avoiding&lt;/em&gt; the usual &lt;code&gt;list&lt;/code&gt;, &lt;code&gt;fold&lt;/code&gt;, etc. solutions—you would use those in Rust for this problem, just the same as in Gleam—but I wanted to use an example from the language tour itself to show that this very same problem is much harder to understand &lt;em&gt;because&lt;/em&gt; of the lack of loops (and mutability).&lt;/p&gt;
&lt;p&gt;This directly demonstrates a major problem of relying on recursion instead of loops: to achieve good performance with recursion, you end up sacrificing readability anyway.&lt;/p&gt;
&lt;p&gt;A secondary problem with missing loops (and early returns) is that it's much harder to quit iteration in the middle of something.
In Rust, when you're looping over something, you can quit as soon as you hit a failure case.
That's not really doable when you call &lt;code&gt;list.map&lt;/code&gt; in Gleam.
You can get some similar behavior with &lt;a href="https://hexdocs.pm/gleam_stdlib/gleam/iterator.html"&gt;Iterator&lt;/a&gt;, which is lazily evaluated, but there are performance costs to this as well (while Rust's iterators &lt;a href="https://ntietz.com/blog/rusts-iterators-optimize-footgun/"&gt;optimize to exactly the same binary as loops&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Type aliases lead to confusing/bad error messages.&lt;/strong&gt;
I've run into this problem with Rust, as well.
In both Gleam and Rust, type aliases are simply different names to refer to the same underlying type.
They don't change anything except what keys you press to get that type in your code.&lt;/p&gt;
&lt;p&gt;The problem for me comes in when I try to assign to a variable where one of these aliases is used as the type.
If I assign something that's the &lt;em&gt;wrong&lt;/em&gt; type, the error message gives me the &lt;em&gt;original&lt;/em&gt; type name instead of the alias name in the error message.
This is sometimes confusing to me, because you can have a type show up seemingly out of nowhere, without anything in your code to tie it back to.
Here's an example of code that will do this:&lt;/p&gt;
&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;pub type UserId = Int
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;pub fn main() {
&lt;/span&gt;&lt;span&gt;    let user_id: UserId = &amp;quot;1&amp;quot;
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it produces this error message:&lt;/p&gt;
&lt;pre style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code&gt;&lt;span&gt;  Compiling tour
&lt;/span&gt;&lt;span&gt;error: Type mismatch
&lt;/span&gt;&lt;span&gt;  ┌─ /home/nicole/Code/gleam/tour/src/tour.gleam:4:27
&lt;/span&gt;&lt;span&gt;  │
&lt;/span&gt;&lt;span&gt;4 │     let user_id: UserId = &amp;quot;1&amp;quot;
&lt;/span&gt;&lt;span&gt;  │                           ^^^
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;Expected type:
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    Int
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;Found type:
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;    String
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I think having &lt;em&gt;both&lt;/em&gt; types would be helpful, because as it stands I often run into confusion with this.
It tells us it expected an &lt;code&gt;Int&lt;/code&gt;, but &lt;em&gt;I&lt;/em&gt; told it to expect a &lt;code&gt;UserId&lt;/code&gt;!
This is most problematic when the alias itself is defined in a library (not directly in my code) and I don't even &lt;em&gt;realize&lt;/em&gt; it's an alias.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The differing number systems in JavaScript and the BEAM.&lt;/strong&gt;
One of the quirks of targeting multiple platforms is that each platform is different, and each one has its own quirks.
While BEAM has some of these, JavaScript has &lt;em&gt;far more&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;In particular, you get JavaScript's numbers, which are, uh, well they're all just IEEE 754 floating point numbers, because why would you want anything else?
This means that you can only have 53-bit integers if you target JavaScript, before things start behaving oddly.
In contrast, when you target the BEAM, you get unbounded big integers!&lt;/p&gt;
&lt;p&gt;On the other hand, the BEAM has its own warts.
Overflowing a float raises an error, but Gleam doesn't have exception handling, so it just crashes out (instead of returning a result type).
For example, this code will simply crash:&lt;/p&gt;
&lt;pre class="language-gleam " style="background-color: #282828; color: #fdf4c1aa;"&gt;&lt;code class="language-gleam"&gt;&lt;span&gt;import gleam/float
&lt;/span&gt;&lt;span&gt;import gleam/io
&lt;/span&gt;&lt;span&gt;
&lt;/span&gt;&lt;span&gt;pub fn main() {
&lt;/span&gt;&lt;span&gt;    io.debug(float.power(1000.0, 1000.0))
&lt;/span&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I would expect that since &lt;code&gt;float.power&lt;/code&gt; returns a &lt;code&gt;Result&lt;/code&gt;, if this fails it will return an error case, but since the standard library is implemented in Gleam itself and Gleam has no way to catch a runtime exception, you cannot do this.&lt;/p&gt;
&lt;p&gt;So, when you work with numbers in Gleam, you're going to have to first work around the quirks of whichever target you're using, and second possibly work around the quirks of &lt;em&gt;multiple&lt;/em&gt; targets.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The approach to parenthesization/grouping is clever, and that cleverness is not worth it.&lt;/strong&gt;
Each unusual choice you make in a language comes with a cost.
And that's why I'm so nonplussed about the choice of using &lt;code&gt;{&lt;/code&gt; and &lt;code&gt;}&lt;/code&gt; for grouping in arithmetic expressions.
In every other language I've used, you can use parentheses to do grouping: &lt;code&gt;(1 + 2) * 3&lt;/code&gt;.
But in Gleam, you have to group these with curly braces: &lt;code&gt;{ 1 + 2 } * 3&lt;/code&gt;.
This is something I would really struggle to get used to, and I don't think I'm alone.&lt;/p&gt;
&lt;p&gt;From a conversation I had with the language's creator, this one comes from how Gleam doesn't use statement/expression terminators.
In many languages, you either detect whitespace to end a statement, or you look for specific punctuation (usually &lt;a href="https://ntietz.com/blog/researching-why-we-use-semicolons-as-statement-terminators/"&gt;the semicolon&lt;/a&gt;).
Gleam's grammar doesn't require this.
For reasons I don't &lt;em&gt;entirely&lt;/em&gt; understand, that does mean that using parentheses for grouping would be hard and would change how it's parsed.&lt;/p&gt;
&lt;p&gt;So instead, we get this!
And I think it's a little bit clever&lt;sup class="footnote-reference" id="fr-use-1"&gt;&lt;a href="https://ntietz.com/blog/first-impressions-of-gleam/#fn-use"&gt;[4]&lt;/a&gt;&lt;/sup&gt;:
The language is expression-oriented and blocks return values, so if you do &lt;code&gt;{ 1 + 2 }&lt;/code&gt; it's already going to return a number.
So this block orientation &lt;em&gt;already&lt;/em&gt; exists, and we can use it to group things as well without fundamental changes to the language.
And I think that ultimately it's a mistake for ergonomics, because it will be rather different (to write &lt;em&gt;and&lt;/em&gt; to read) from what most people are familiar with.&lt;/p&gt;
&lt;h1 id="go-learn-some-gleam"&gt;Go learn some Gleam!&lt;/h1&gt;
&lt;p&gt;Gleam is a wonderful little language and community.
I hope it continues to grow and that it thrives.
Learning it has given me a lot of ideas that I want to carry forward, and it's given me yet another language I can use to solve problems.
If you have a free afternoon, you should try it out!&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Thank you to &lt;a href="https://lpil.uk/"&gt;Louis Pilfold&lt;/a&gt; and &lt;a href="https://erikarow.land/"&gt;Erika Rowland&lt;/a&gt; for feedback on a draft of this post!&lt;/p&gt;
&lt;hr /&gt;
&lt;hr /&gt;&lt;ol class="footnotes-list"&gt;
&lt;li id="fn-except-sick"&gt;
&lt;p&gt;I started poking away at the language tour almost a month ago.
It only actually takes a few hours to read through, probably.
It's really fast!
But I've been sick, and it took me most of a month to read it, take some notes, and write this up. &lt;a href="https://ntietz.com/blog/first-impressions-of-gleam/#fr-except-sick-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-as-erika-says"&gt;
&lt;p&gt;This point is very well made in &lt;a href="https://erikarow.land/notes/gleam-favorite-feature"&gt;Erika's post about Gleam's best features&lt;/a&gt;, which is another good read. &lt;a href="https://ntietz.com/blog/first-impressions-of-gleam/#fr-as-erika-says-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-hello-lilac"&gt;
&lt;p&gt;That's one of the reasons I'm working on my language, Lilac.
I want to achieve two things.
One is to make a language that has a lot of what I like from Rust (and Gleam!) without some of the things I find hard to use.
The other, though, is to gain a deeper understanding of &lt;em&gt;why&lt;/em&gt; some of the things I don't like are done the way they are.
It's easier to use a tool when you understand why it is the way it is. &lt;a href="https://ntietz.com/blog/first-impressions-of-gleam/#fr-hello-lilac-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn-use"&gt;
&lt;p&gt;This is also true for the &lt;a href="https://erikarow.land/notes/using-use-gleam"&gt;use keyword&lt;/a&gt;.
The way I feel about &lt;code&gt;use&lt;/code&gt; in Gleam is: this is very clever, and it's a problem that didn't &lt;em&gt;have&lt;/em&gt; to exist in the language.
It avoids expanding the language somewhat, but is hard to understand and lacks some of the expressive power of adding other language constructs. &lt;a href="https://ntietz.com/blog/first-impressions-of-gleam/#fr-use-1"&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description><author>ntietz.com blog - technically a blog</author><pubDate>Mon, 05 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://ntietz.com/blog/first-impressions-of-gleam/?utm_source=atom&amp;utm_medium=feed</guid></item><item><title>21 More AWS Services They Should Cancel</title><link>https://justingarrison.com/blog/2024-08-05-more-aws-services-they-should-cancel/</link><description>Please Amazon 🙏 kill these services too.</description><author>Justin Garrison's Homepage</author><pubDate>Mon, 05 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://justingarrison.com/blog/2024-08-05-more-aws-services-they-should-cancel/</guid></item><item><title>Goodbye Microsoft Hello Facebook</title><link>https://saeedesmaili.com/notes/goodbye-microsoft-hello-facebook/</link><description>&lt;p&gt;Philip writes about how small cost saving policies at Microsoft irritated him as an employee:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We used to get Dove Bars and beers all the time. It felt like free food was on offer at least once a week, usually with a pretense of some small milestone to celebrate. Why did we cut stuff like this? (I know the boring fiscal reasons why. I&amp;rsquo;m asking the deeper &lt;em&gt;why&lt;/em&gt;, as in, &amp;ldquo;Was it worth the savings? Is Microsoft better now that we&amp;rsquo;ve cut these costs?&amp;rdquo;)&lt;/p&gt;</description><author>Saeed Esmaili</author><pubDate>Sun, 04 Aug 2024 18:40:40 GMT</pubDate><guid isPermaLink="true">https://saeedesmaili.com/notes/goodbye-microsoft-hello-facebook/</guid></item><item><title>Blog inspiration: Jeff Kaufman</title><link>https://evanfields.net/Blog-Inspiration-Jeff/</link><description>I’ve been reading Jeff Kaufman’s blog for years because he’s well known in the effective altruist1 space and local to the Boston area. We’re also now coworkers, which is pretty cool. One of many things I like about Jeff’s blog: he interleaves many different kinds of posts. Some are deeply technical or philosophical, but others are quick notes about what’s going on in his life. This variety imbues the blog with a pleasant texture. I’m peripheral and sympathetic, or sorta an EA, or an EA who doesn’t want to extrapolate logic to its extreme, or something like that. I keep meaning to write about this, but the words don’t come naturally…which is presumably a reason to write about it. &amp;#8617;</description><author>Evan Fields</author><pubDate>Sun, 04 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://evanfields.net/Blog-Inspiration-Jeff/</guid></item><item><title>Neuromancer</title><link>https://vit.baisa.cz/books/neuromancer/</link><description>the archetypal cyberpunk work</description><author>Vít Baisa</author><pubDate>Sun, 04 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://vit.baisa.cz/books/neuromancer/</guid></item><item><title>If you have to optimize, it isn't good enough</title><link>https://notes.npilk.com/optimizing-isnt-good-enough</link><description>Success is determined by your initial conditions, not whatever improvements you can make on the margin.</description><author>npilk // Notes</author><pubDate>Sat, 03 Aug 2024 22:00:00 GMT</pubDate><guid isPermaLink="true">https://notes.npilk.com/optimizing-isnt-good-enough</guid></item><item><title>Monthly resolutions</title><link>https://notes.npilk.com/monthly-resolutions</link><description>A few years ago, instead of a New Year's resolution, I experimented with setting twelve: one for each month of the year. I liked it much more than setting traditional resolutions, and I'm planning to try it again this year.</description><author>npilk // Notes</author><pubDate>Sat, 03 Aug 2024 21:55:00 GMT</pubDate><guid isPermaLink="true">https://notes.npilk.com/monthly-resolutions</guid></item><item><title>The Chronicles of Gerald Caligula</title><link>https://maximumeffort.substack.com/p/the-chronicles-of-gerald-caligula</link><description>Chapter One: Bellum Contra Neptunum</description><author>Maximum Effort, Minimum Reward</author><pubDate>Sat, 03 Aug 2024 21:12:23 GMT</pubDate><guid isPermaLink="true">https://maximumeffort.substack.com/p/the-chronicles-of-gerald-caligula</guid></item><item><title>VLM data extraction with Protobufs</title><link>https://www.danielcorin.com/posts/2024/vlm-data-extraction-with-protobufs/</link><description>VLM data extraction with Protobufs</description><author>Thought Eddies</author><pubDate>Sat, 03 Aug 2024 19:37:52 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/posts/2024/vlm-data-extraction-with-protobufs/</guid></item><item><title>Note 141</title><link>https://qubyte.codes/notes/1722690472447</link><description>&lt;p&gt;My partner treated me to a couple of days stay in the Tokyo Station Hotel for my birthday. We’re in the south dome, and I can see the gates and folk come and go from up here. I don’t know how they did it, but it’s totally silent here. Absolutely no station noise at all!&lt;/p&gt;

    &lt;img alt="The view from a room in the south dome of the Tokyo Station Hotel. It’s a few stories above the concourse, where people can be seen entering the automated gates. Ticket machines can also be seen." src="https://qubyte.codes/images/1722690173183.jpeg" /&gt;</description><author>Qubyte Codes</author><pubDate>Sat, 03 Aug 2024 16:07:52 GMT</pubDate><guid isPermaLink="true">https://qubyte.codes/notes/1722690472447</guid></item><item><title>Protobuf Zip Imports in Python</title><link>https://www.danielcorin.com/til/protobuf/zip-imports/</link><description>Protobuf Zip Imports in Python</description><author>Thought Eddies</author><pubDate>Sat, 03 Aug 2024 13:18:43 GMT</pubDate><guid isPermaLink="true">https://www.danielcorin.com/til/protobuf/zip-imports/</guid></item><item><title>Changing Chrome Address Bar Suggestions</title><link>https://blog.herlein.com/post/chrome-predictor/</link><description>&lt;p&gt;Leaving this here mostly for me, but short instructions on how to change Chrome&amp;rsquo;s &amp;ldquo;typeahead&amp;rdquo; predictors if it remembers the &amp;ldquo;wrong&amp;rdquo; one.&lt;/p&gt;</description><author>Greg Herlein</author><pubDate>Sat, 03 Aug 2024 11:00:01 GMT</pubDate><guid isPermaLink="true">https://blog.herlein.com/post/chrome-predictor/</guid></item><item><title>Emacs packages</title><link>https://zellyn.com/2024/08/emacs-packages/</link><description>&lt;p&gt;Now that &lt;a href="https://www.masteringemacs.org/article/whats-new-in-emacs-29-1"&gt;Emacs
29&lt;/a&gt;
moved so many useful things into Emacs proper, it&amp;rsquo;s time to declare
config bankruptcy and start over, with a newer, cleaner &lt;code&gt;init.el&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So, I need to understand how Emacs packages work.&lt;/p&gt;
&lt;p&gt;Like, what does
&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/use-package/index.html"&gt;use-package&lt;/a&gt;
do? What is &lt;a href="https://github.com/progfolio/elpaca"&gt;Elpaca&lt;/a&gt; for? And is
it worth straying from the vanilla path?&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what I&amp;rsquo;ve put together so far&amp;hellip;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="packages"&gt;Packages&lt;/h2&gt;
&lt;p&gt;Emacs packages serve roughly the same function as packages in other
languages. The organized package repositories are named some variant
of &amp;ldquo;ELPA&amp;rdquo; (&amp;ldquo;Emacs Lisp Package Archive&amp;rdquo;)&lt;sup id="fnref:1"&gt;&lt;a class="footnote-ref" href="#fn:1"&gt;1&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;aside&gt;
&lt;h3 id="aside-a-recurring-theme-startup-time"&gt;Aside: A recurring theme: startup time&lt;/h3&gt;
&lt;p&gt;Several aspects of the package system appear to have been motivated by
wanting Emacs to start more quickly. Until Emacs 26, Elisp was
single-threaded&lt;sup id="fnref:2"&gt;&lt;a class="footnote-ref" href="#fn:2"&gt;2&lt;/a&gt;&lt;/sup&gt;, and until Emacs 28, it was only interpreted.&lt;/p&gt;
&lt;p&gt;The most striking example is the way &lt;code&gt;use-package&lt;/code&gt; loads packages only
on use, rather than on startup.&lt;/p&gt;
&lt;/aside&gt;
&lt;h2 id="step-one-installing-packages-manually"&gt;Step one: installing packages manually&lt;/h2&gt;
&lt;p&gt;In the beginning, packages were installed manually. Xah Lee has &lt;a href="http://xahlee.info/emacs/emacs/emacs_installing_packages.html"&gt;a
guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="step-two-use-a-package-manager"&gt;Step two: use a package manager&lt;/h2&gt;
&lt;p&gt;As near as I can figure, in 2007, Tom Tromey created &lt;code&gt;package.el&lt;/code&gt; and
ELPA. &lt;a href="https://tromey.com/blog/?p=342"&gt;Both&lt;/a&gt; are
&lt;a href="https://tromey.com/blog/?p=345"&gt;mentioned&lt;/a&gt; in blog posts dating to
April of 2007.&lt;/p&gt;
&lt;p&gt;In version 24, Emacs started shipping with &lt;code&gt;package.el&lt;/code&gt; included.
&lt;code&gt;M-x list-packages&lt;/code&gt; will bring up the package manager, and let you
browse and install.&lt;/p&gt;
&lt;h3 id="package-archives"&gt;Package archives&lt;/h3&gt;
&lt;p&gt;There are several flavors of ELPA:&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;GNU ELPA&lt;/dt&gt;
&lt;dd&gt;&lt;a href="https://elpa.gnu.org"&gt;elpa.gnu.org&lt;/a&gt; - requires copyright
assignment. Essentially, packages here are considered part of Emacs,
but not distributed by default. 443 packages.&lt;/dd&gt;
&lt;dt&gt;NonGNU ELPA&lt;/dt&gt;
&lt;dd&gt;&lt;a href="elpa.nongnu.org"&gt;https://elpa.nongnu.org&lt;/a&gt; - requires
GPLv3-compatible code and GNU FDL v1.4-compatible documentation. 235 packages.&lt;/dd&gt;
&lt;dt&gt;MELPA&lt;/dt&gt;
&lt;dd&gt;&lt;a href="https://melpa.org"&gt;melpa.org&lt;/a&gt; - anything goes. Just open a
PR to add a package. 5,796 packages.&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;&lt;code&gt;package.el&lt;/code&gt; defaults to showing
packages from GNU ELPA and NonGNU ELPA.&lt;/p&gt;
&lt;h2 id="step-three-more-powerful-package-managers"&gt;Step three: more powerful package managers&lt;/h2&gt;
&lt;p&gt;From there, a variety of different package managers exist, with
varying capabilities and aras of focus. (Get more control over package
sources? Install from git? Help you &lt;em&gt;develop&lt;/em&gt; packages?) The
&lt;code&gt;straight.el&lt;/code&gt; &lt;a href="https://github.com/radian-software/straight.el?tab=readme-ov-file#comparison-to-other-package-managers"&gt;comparison to other package
managers&lt;/a&gt;
docs and following &lt;a href="https://github.com/radian-software/straight.el?tab=readme-ov-file#tldr-1"&gt;TL;DR
section&lt;/a&gt;
give an excellent overview.&lt;/p&gt;
&lt;h2 id="step-four-use-package"&gt;Step four: &lt;code&gt;use-package&lt;/code&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;use-package&lt;/code&gt;, created by John Wiegley in 2012, offers concise, tidy
syntax for using packages. It seems to have won, and was merged into
Emacs in 2022, for Emacs 29.&lt;/p&gt;
&lt;p&gt;A &lt;code&gt;use-package&lt;/code&gt; call lets you easily register a package, installing if
necessary. In that same single call you can also:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Configure the package&lt;/li&gt;
&lt;li&gt;Set up keybindings or entire keymaps&lt;/li&gt;
&lt;li&gt;Map file types to modes&lt;/li&gt;
&lt;li&gt;Add hooks&lt;/li&gt;
&lt;li&gt;more&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Modern package managers integrate with &lt;code&gt;use-package&lt;/code&gt;, replacing its
default installation mechanism.&lt;/p&gt;
&lt;p&gt;More information on &lt;code&gt;use-package&lt;/code&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.gnu.org/software/emacs/manual/html_node/use-package/index.html"&gt;User Manual (info)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jwiegley/use-package#readme"&gt;github README&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.masteringemacs.org/article/spotlight-use-package-a-declarative-configuration-tool"&gt;Mastering Emacs
article&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="step-five-package-managers-so-powerful-its-getting-silly"&gt;Step five: package managers so powerful, it&amp;rsquo;s getting silly&lt;/h2&gt;
&lt;p&gt;If you search for modern advice on which package manager to use,
you&amp;rsquo;ll see a lot of articles on using
&lt;a href="https://github.com/radian-software/straight.el"&gt;&lt;code&gt;straight.el&lt;/code&gt;&lt;/a&gt;. But,
the current maintainer of &lt;code&gt;straight.el&lt;/code&gt; then went on to write
&lt;a href="https://github.com/progfolio/elpaca"&gt;Elpaca&lt;/a&gt;. Their &lt;a href="https://www.reddit.com/r/emacs/comments/19bs8w9/comment/kiu27mo/"&gt;reddit
comparison&lt;/a&gt;
is useful. I believe Elpaca is the current state of the art. It can
install packages asynchronously, in parallel!&lt;sup id="fnref:3"&gt;&lt;a class="footnote-ref" href="#fn:3"&gt;3&lt;/a&gt;&lt;/sup&gt;&lt;/p&gt;
&lt;h2 id="final-comments"&gt;Final comments&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;m currently using Elpaca, although since I don&amp;rsquo;t do anything fancy,
I should probably just use &lt;code&gt;package.el&lt;/code&gt;. Elpaca&amp;rsquo;s async loading
notifications look cool&amp;hellip;&lt;/p&gt;
&lt;p&gt;I asked emacs stackexchange to &amp;ldquo;Explain Elpaca Like I&amp;rsquo;m 5&amp;rdquo; and the
&lt;a href="https://emacs.stackexchange.com/questions/81730"&gt;reponses&lt;/a&gt; provided
interesting perspectives and a good starting point.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;That&amp;rsquo;s it! If you see a mistake, or think something should be added,
clarified, or corrected, please let me know. I&amp;rsquo;d like this post to be
the thing I wish I&amp;rsquo;d found at the
start. &lt;a href="http://zellyn.com/about#contact"&gt;zellyn.com/about#contact&lt;/a&gt;&lt;/p&gt;
&lt;div class="footnotes"&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;The Comprehensive Perl Archive Network
(&lt;a href="https://www.cpan.org"&gt;CPAN&lt;/a&gt;) is probably the most well-known
&amp;ldquo;Package Archive&amp;rdquo; package system, and was inspired by TeX&amp;rsquo;s
&lt;a href="https://ctan.org/?lang=en"&gt;CTAN&lt;/a&gt;.&amp;#160;&lt;a class="footnote-backref" href="#fnref:1"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;Multi-threading: it&amp;rsquo;s not clear whether you should use
it (yet?). See Troy Hinckley&amp;rsquo;s &amp;ldquo;&lt;a href="https://coredumped.dev/2022/05/19/a-vision-of-a-multi-threaded-emacs/"&gt;A vision of a multi-threaded
Emacs&lt;/a&gt;&amp;rdquo;,
and Tom Tromey&amp;rsquo;s &lt;a href="https://www.reddit.com/r/emacs/comments/utzxir/comment/i9diqxr/"&gt;Reddit
comment&lt;/a&gt;,
both from May, 2022.&amp;#160;&lt;a class="footnote-backref" href="#fnref:2"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;Installing packages asynchronously, in parallel, is neat,
although you don&amp;rsquo;t really &lt;em&gt;install&lt;/em&gt; packages that often&amp;hellip; 🤷‍♂️&amp;#160;&lt;a class="footnote-backref" href="#fnref:3"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</description><author>Zellyn Hunter</author><pubDate>Sat, 03 Aug 2024 04:38:00 GMT</pubDate><guid isPermaLink="true">https://zellyn.com/2024/08/emacs-packages/</guid></item><item><title>MicroGrad.jl: Part 1 ChainRules</title><link>https://liorsinai.github.io/machine-learning/2024/07/27/micrograd-1-chainrules.html</link><description>A series on automatic differentiation in Julia. Part 1 provides an overview and defines explicit chain rules.</description><author>Lior Sinai</author><pubDate>Sat, 03 Aug 2024 03:00:00 GMT</pubDate><guid isPermaLink="true">https://liorsinai.github.io/machine-learning/2024/07/27/micrograd-1-chainrules.html</guid></item><item><title>Debloating the Onyx Boox Go 10.3</title><link>https://appsec.space/posts/onyx-boox-go-10.3/</link><description>&lt;div class="featured-image"&gt;
                &lt;img src="/posts/onyx-boox-go-10.3/boox_go_cover.jpg" /&gt;
            &lt;/div&gt;&lt;p&gt;I was looking for an eink tablet to r
ead books and take notes while I&amp;rsquo;m away from home.&lt;/p&gt;
&lt;p&gt;After adventuring in the eInk rabbit hole I decided to go for the &lt;a href="https://amzn.to/3SC2W6Z" rel="noopener noreferrer" target="_blank"&gt;Onyx Boox Go 10.3&lt;/a&gt;: a Black and White eInk Android Tablet with 300ppi that&amp;rsquo;s also good for taking notes, weighting only 365g!&lt;/p&gt;
&lt;p&gt;I was a bit concerned about &lt;a href="https://foundation.mozilla.org/en/privacynotincluded/onyx-boox/" rel="noopener noreferrer" target="_blank"&gt;this report from Mozilla&lt;/a&gt; so I decided to take a look at the device.&lt;/p&gt;
&lt;div class="details admonition danger open"&gt;
    &lt;div class="details-summary admonition-title"&gt;
        &lt;span class="icon"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M439.15 453.06L297.17 384l141.99-69.06c7.9-3.95 11.11-13.56 7.15-21.46L432 264.85c-3.95-7.9-13.56-11.11-21.47-7.16L224 348.41 37.47 257.69c-7.9-3.95-17.51-.75-21.47 7.16L1.69 293.48c-3.95 7.9-.75 17.51 7.15 21.46L150.83 384 8.85 453.06c-7.9 3.95-11.11 13.56-7.15 21.47l14.31 28.63c3.95 7.9 13.56 11.11 21.47 7.15L224 419.59l186.53 90.72c7.9 3.95 17.51.75 21.47-7.15l14.31-28.63c3.95-7.91.74-17.52-7.16-21.47zM150 237.28l-5.48 25.87c-2.67 12.62 5.42 24.85 16.45 24.85h126.08c11.03 0 19.12-12.23 16.45-24.85l-5.5-25.87c41.78-22.41 70-62.75 70-109.28C368 57.31 303.53 0 224 0S80 57.31 80 128c0 46.53 28.22 86.87 70 109.28zM280 112c17.65 0 32 14.35 32 32s-14.35 32-32 32-32-14.35-32-32 14.35-32 32-32zm-112 0c17.65 0 32 14.35 32 32s-14.35 32-32 32-32-14.35-32-32 14.35-32 32-32z"&gt;&lt;/svg&gt;&lt;/span&gt;Danger&lt;span class="details-icon"&gt;&lt;svg class="icon" viewBox="0 0 256 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M224.3 273l-136 136c-9.4 9.4-24.6 9.4-33.9 0l-22.6-22.6c-9.4-9.4-9.4-24.6 0-33.9l96.4-96.4-96.4-96.4c-9.4-9.4-9.4-24.6 0-33.9L54.3 103c9.4-9.4 24.6-9.4 33.9 0l136 136c9.5 9.4 9.5 24.6.1 34z"&gt;&lt;/svg&gt;&lt;/span&gt;
    &lt;/div&gt;
    &lt;div class="details-content"&gt;
        &lt;div class="admonition-content"&gt;Boox is using a custom version of the kernel &lt;a href="https://news.ycombinator.com/item?id=23735962" rel="noopener noreferrer" target="_blank"&gt;and it&amp;rsquo;s not sharing the source&lt;/a&gt; so act accordingly.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
&lt;h2 class="headerLink" id="enabling-adb"&gt;
    &lt;a class="header-mark" href="#enabling-adb"&gt;&lt;/a&gt;17 Enabling ADB&lt;/h2&gt;&lt;p&gt;The Onyx default Launcher is hiding a lot of the Android inner features but reading a bit of documentation I figured out how to enable ADB.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: Press the &lt;code&gt;Apps&lt;/code&gt; Icon on the left -&amp;gt; Top right icon -&amp;gt; &lt;code&gt;App Management&lt;/code&gt; -&amp;gt; Enable &amp;ldquo;USB Debug Mode&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Connecting the device via usb and running &lt;code&gt;adb shell&lt;/code&gt; will open a shell on the device.&lt;/p&gt;
&lt;figure&gt;&lt;img src="/posts/onyx-boox-go-10.3/onyx_settings.jpg" /&gt;&lt;figcaption&gt;
      &lt;h4&gt;Enable ADB via Onyx Launcher&lt;/h4&gt;
    &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 class="headerLink" id="removing-unnecessary-apps"&gt;
    &lt;a class="header-mark" href="#removing-unnecessary-apps"&gt;&lt;/a&gt;18 Removing unnecessary apps&lt;/h2&gt;&lt;p&gt;Navigating the UI I figured out the tablet is full of useless (for me) apps, so I decided to remove them.
After &lt;a href="https://www.xda-developers.com/install-adb-windows-macos-linux/#how-to-set-up-adb-on-your-computer" rel="noopener noreferrer" target="_blank"&gt;installing adb&lt;/a&gt; I removed the unwanted apps with the following commands:&lt;/p&gt;
&lt;div class="code-block highlight is-open show-line-numbers  tw-group tw-my-2"&gt;
  &lt;div class="
    
    tw-flex 
    tw-flex-row
    tw-flex-1 
    tw-justify-between 
    tw-w-full tw-bg-bgColor-secondary
    "&gt;      
    &lt;button class="
        code-block-button
        tw-mx-2 
        tw-flex
        tw-flex-row
        tw-flex-1"&gt;
          &lt;div class="group-[.is-open]:tw-rotate-90 tw-transition-[transform] tw-duration-500 tw-ease-in-out print:!tw-hidden tw-w-min tw-h-min tw-my-1 tw-mx-1"&gt;&lt;svg class="icon" viewBox="0 0 320 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M285.476 272.971L91.132 467.314c-9.373 9.373-24.569 9.373-33.941 0l-22.667-22.667c-9.357-9.357-9.375-24.522-.04-33.901L188.505 256 34.484 101.255c-9.335-9.379-9.317-24.544.04-33.901l22.667-22.667c9.373-9.373 24.569-9.373 33.941 0L285.475 239.03c9.373 9.372 9.373 24.568.001 33.941z"&gt;&lt;/svg&gt;&lt;/div&gt;
          &lt;p class="tw-select-none !tw-my-1"&gt;adb&lt;/p&gt;
      &lt;/button&gt;

   &lt;div class="tw-flex"&gt;
      &lt;button class="
          line-number-button
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.show-line-numbers]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle line numbers"&gt;&lt;svg class="icon" viewBox="0 0 512 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M61.77 401l17.5-20.15a19.92 19.92 0 0 0 5.07-14.19v-3.31C84.34 356 80.5 352 73 352H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8h22.83a157.41 157.41 0 0 0-11 12.31l-5.61 7c-4 5.07-5.25 10.13-2.8 14.88l1.05 1.93c3 5.76 6.29 7.88 12.25 7.88h4.73c10.33 0 15.94 2.44 15.94 9.09 0 4.72-4.2 8.22-14.36 8.22a41.54 41.54 0 0 1-15.47-3.12c-6.49-3.88-11.74-3.5-15.6 3.12l-5.59 9.31c-3.72 6.13-3.19 11.72 2.63 15.94 7.71 4.69 20.38 9.44 37 9.44 34.16 0 48.5-22.75 48.5-44.12-.03-14.38-9.12-29.76-28.73-34.88zM496 224H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zm0-160H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16V80a16 16 0 0 0-16-16zm0 320H176a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h320a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM16 160h64a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H64V40a8 8 0 0 0-8-8H32a8 8 0 0 0-7.14 4.42l-8 16A8 8 0 0 0 24 64h8v64H16a8 8 0 0 0-8 8v16a8 8 0 0 0 8 8zm-3.91 160H80a8 8 0 0 0 8-8v-16a8 8 0 0 0-8-8H41.32c3.29-10.29 48.34-18.68 48.34-56.44 0-29.06-25-39.56-44.47-39.56-21.36 0-33.8 10-40.46 18.75-4.37 5.59-3 10.84 2.8 15.37l8.58 6.88c5.61 4.56 11 2.47 16.12-2.44a13.44 13.44 0 0 1 9.46-3.84c3.33 0 9.28 1.56 9.28 8.75C51 248.19 0 257.31 0 304.59v4C0 316 5.08 320 12.09 320z"&gt;&lt;/svg&gt;&lt;/button&gt;

      &lt;button class="
          wrap-code-button
          tw-select-none 
          tw-mx-2 
          tw-hidden 
          group-[.is-open]:tw-block 
          group-[.is-wrap]:tw-text-fgColor-link 
          print:!tw-hidden" title="Toggle code wrap"&gt;&lt;svg class="icon" viewBox="0 0 448 512" xmlns="http://www.w3.org/2000/svg"&gt;&lt;!-- Font Awesome Free 5.15.4 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --&gt;&lt;path d="M16 132h416c8.837 0 16-7.163 16-16V76c