More from Daniel De Laney
The way I used to design UI was to sit with paper sketches, or Figma, or a React prototype with no back end. Imagine every failure mode. Design beautiful flows for each. I slipped into this mistake while working on LanWhisper, a voice dictation app. I designed for the failure modes I could imagine, drew the flows, and shipped the app. Then real users hit failure modes that I hadn’t anticipated. For example, the AI transcription model can return hallucinated text even if the input audio is empty. I had imagined that if the model returned text at all then the app was working. This failure wasn’t in my carefully designed list of ideal failures. Whoops. The problem is that a representation of a system only contains what you put into it. The failure modes it shows you are exclusively the ones you already thought of. This is the same mistake as designing for users you’ve never talked to. Designers know that’s a trap. The same logic applies to the systems we design: imagined systems aren’t a substitute for real ones. When designing non-trivial systems, imagination is no longer your best source of truth. The system itself is. And increasingly, designers can build it themselves. Do. Build it, instrument it, surface every piece of state. You can see and feel how the system works instead of projecting the ideal behavior onto some drawings you linked together. Your list of ideal failure modes isn’t real.
Have you ever gotten to the end of a long work day and realized you’re no closer to your goals? I have. Sure, I was doing a lot of stuff. But I wasn’t pausing to ask whether I was doing the right stuff. Or whether my approach was working. Or if I was spending the right amount of time on it. My fingers were moving but I wasn’t really thinking. So I needed a reliable way to interrupt my “unproductive productivity” and actually think. The obvious solution was a timer. Unfortunately, if you use timers a lot, you learn to dismiss them reflexively. And it’s really easy to forget to set the next timer. A week later, I’d realize: “Hey, that timer idea really worked, I should get back to that.” And then I didn’t. So I built a new kind of timer. It does 2 unique things: It asks what I’ll focus on. It gradually blurs my screen if I don’t set a new timer. When it asks “What will you focus on?” I answer in a word or two, start the next timer, and keep working. Having to name my intention keeps me fully aware of my trajectory. If I’m in danger of drifting, it’s obvious. And if I avoid thinking for long enough, my screen starts getting harder to see. If I’m making great progress on something that doesn’t require much thinking, I can set the timer for a longer duration, maybe 30 minutes. But if I’m working on something more open-ended, I might tighten the leash all the way down to 3 minutes. Then I can’t get off track. Unlike a regular timer, I can’t fail to set the next one. If I don’t answer it promptly, the screen gradually becomes less readable until I do. If I wanted to avoid answering, I’d have to make a conscious decision to close the app. I’d have to decide to be less productive. I never do. This small intervention has worked beautifully. Not only am I catching unproductive divergences earlier, I’m noticing fewer of them over time. It seems to be training me to do more and better thinking. It’s not a replacement for a journal. I love journaling, but that takes more than a few seconds, and there’s a lot of benefit to reflecting more frequently. If you’re running macOS, Intention is available here. I use it every day, and I think it’s the superior way of working. Process Tools like Cursor and Claude Code have dramatically changed the way I approach the design process. In the past, I would have sketched some potential solutions, put together a clickable prototype, and user tested that. But how much does that user test actually test? A traditional prototype in Figma or the like is paper-thin. I’m testing much less than the full experience. Now I can sculpt functional software as I go. That means I can build something, really use it, notice opportunities to make it better, and implement the changes I’d like to see, all in the same working session. This fast and robust feedback loop means better software. That said, sketches are still faster. I’ll bounce back and forth between sketching and building as appropriate. It’s far more practical to draw usable imagery than to generate it. The dock icon is drawn by hand in Figma with the pen tool and layer effects. The candle in the dark serves as both a metaphor for the app’s purpose illuminating the way forward, and an allusion to meditative practice. Why does this menu bar application appear in the dock? The answer is in the riddle of keyboard focus. An app that steals my keyboard focus every 3 minutes would be impossible to use, so instead the appearance of the timer window in the top right of the screen gently reminds me, and the gradual blurring of the screen gets more insistent over time. But the app does not take keyboard focus itself. This balance is what makes the app work. So I need a fast and intuitive way to switch to the timer window when I’m ready. Cmd+Tab has to work, and having the app appear in the dock enables that. Inspiration The visual inspiration for the branding is the early 1600s Caravaggio painting Saint Jerome Writing. The aging scholar Jerome, remembering the nearness of death, absorbs himself completely in the most noble work he can find to do while ignoring everything else. This is our task.
I’m the person my friends and family come to for computer-related help. (Maybe you, gentle reader, can relate.) This experience has taught me which computing tasks are frustrating for normal people. Normal people often struggle with converting video. They will need to watch, upload, or otherwise do stuff with a video, but the format will be weird. (Weird, broadly defined, is anything that won’t play in QuickTime or upload to Facebook.) I would love to recommend Handbrake to them, but the user interface is by and for power users. Opening it makes normal people feel unpleasant feelings. This problem is rampant in free software. The FOSS world is full of powerful tools that only have a “power user” UI. As a result, people give up. Or worse: they ask people like you and I to do it for them. I want to make the case to you that you can (and should) solve this kind of problem in a single evening. Take the example of Magicbrake, a simple front end I built. It hides the power and flexibility of Handbrake. It does only the one thing most people need Handbrake for: taking a weird video file and making it normal. (Normal, for our purposes, means a small MP4 that works just about anywhere.) There is exactly one button. This is a fast and uncomplicated thing to do. Unfortunately, the people who have the ability to solve problems like this are often disinclined to do it. “Why would you make Handbrake less powerful on purpose?” “What if someone wants a different format?” “What about [feature/edge case]?” The answer to all these questions is the same: a person who needs or wants that stuff can use Handbrake. If they don’t need everything Handbrake can do and find it bewildering, they can use this. Everyone wins. It’s a bit like obscuring the less-used functions on a TV remote with tape. The functions still exist if you need them, but you’re not required to contend with them just to turn the TV on. People benefit from stuff like this, and I challenge you to make more of it. Opportunities are everywhere. The world is full of media servers normal people can’t set up. Free audio editing software that requires hours of learning to be useful for simple tasks. Network monitoring tools that seem designed to ward off the uninitiated. Great stuff normal people don’t use. All because there’s only one UI, and it’s designed to do everything. 80% of the people only need 20% of the features. Hide the rest from them and you’ll make them more productive and happy. That’s really all it takes.
An objective, external world is a non-falsifiable assumption. The prevailing theory is that our subjective experiences correspond to an external reality. However, they may simply be subjective through and through. That which we claim to be evidence of external reality is actually subjective experience, which may or may not have an external and objective cause. Any test devised to prove objectivity is evaluated within subjectivity and therefore does not require objectivity to explain the result. Some object to this, claiming that the consistency of experience is best explained by an external world. However, consistent experience does not require any external mechanism, let alone the specific one we have assumed. Claiming that belief in an external world is simpler is like claiming that belief in God is simpler; in truth we are inventing something vast and complex without evidence and agreeing not to question it. This is not science, it is a substitute for epistemic humility. Much as dreams appear consistent while dreaming, that which we consider waking experience may not actually be as consistent as we believe. However, questioning this is unproductive reasoning because it undermines the value of reason itself. We must assume our experiences are rational and consistent, or else give up thinking altogether. Experience is the only reality which is detectable. Whatever experience is, it is real and directly perceptible, unlike objectivity. Claims that experience is an illusion presuppose an objective world to which experience does not correspond. Pragmatic truth is supportable, correspondence is not. If an objective world can’t be proven, neither can we prove that knowledge does or does not correspond with it. That which produces a consistent effect in experience is useful in influencing experience in the desired way, therefore science is useful. Materialism is religious faith. Just as we once invented a spirit world to help explain our experiences, we invented an objective world for which there is similar quality evidence. Both are assumed to explain experience, yet neither is directly known. The assertion that matter gives rise to experience is no more compelling than the assertion that experience gives rise to matter. The assumption of an external world has zero explanatory power, as consistent experience does not require it. Materialism is superior to classical religions in that it responds to pragmatic truth, but it still accepts unsupportable metaphysical claims and regards them as unquestionable. By contrast, noting that we have experiences does not require extrapolation or invention. Modern civilization is optimizing materials, not experiences. Focus on economic metrics has allowed us to make tremendous progress in reducing starvation and otherwise improve the experience of the least fortunate. Nonetheless, the subtle error of conflating material improvement with improvement in well-being has consequences. In advanced societies, increases in abstract indicators of material wealth like GDP have been accompanied by negative changes in consciousness: stress, social disconnection, and increased suicide. The materialist assumption that improving external conditions will always trickle down to better experiences is demonstrably unreliable. Often, this assumption results in methods which improve economic indicators by reducing experiential well-being, and in these cases it is worse than nothing. In addition to misallocating its priorities, modern civilization also conditions people to feel powerless over their own well-being. As materialist structures (corporations, governments, economic systems) become more dominant, individuals are increasingly absorbed into mechanisms designed to optimize external conditions rather than subjective experience. People come to believe that their quality of life is dictated by forces beyond their control. The best way to improve experience is to optimize it directly. The only rational goal is maximizing satisfaction. Long-term positive changes in consciousness are what is best in life. If a person achieves material or hedonistic aims but is unsatisfied in the long term, they are having a negative experience and are working against themselves. Secure, nourish, nurture, and build yourself and your community. Seek what is satisfying and aesthetic—that which feels good and true and beautiful. Unlike materialist assumptions, this requires no external faith, only a direct commitment to improving the reality we actually experience.
Code forces humans to be precise. That’s good. Computers need precision. But it also forces humans to think like machines. For decades we tried to fix this by making programming more human-friendly. Higher-level languages. Visual interfaces. Each step helped, but we were still translating human thoughts into computer instructions. AI was supposed to change everything. Finally, plain English could be a programming language. No syntax. No rules. Just say what you want. The first wave of AI coding tools squandered this opportunity. They make flashy demos but produce garbage software. People call them “great for prototyping,” which means “don’t use this for anything real.” Many blame the AI models, saying we just need them to get smarter. This is wrong. Yes, better AI will make better guesses about what you mean. But when you’re building serious software, you don’t want guesses. Not even smart ones. You want to know exactly what you’re building. Current AI tools pretend writing software is like having a conversation. It’s not. It’s like writing laws. You’re using English, but you’re defining terms, establishing rules, and managing complex interactions between everything you’ve said. Try writing a tax code in chat messages. You can’t. Even simple tax codes are too complex to keep in your head. That’s why we use documents—they let us organize complexity, reference specific points, and track changes systematically. Chat reduces you to memory and hope. This is the core problem. You can’t build real software without being precise about what you want. Every successful programming tool in history reflects this truth. AI briefly fooled us into thinking we could just chat our way to complex software. We can’t. You don’t program by chatting. You program by writing documents. When your intent is in a document instead of scattered across a chat log, English becomes a real programming language: You can see your whole system at once You can clarify and improve your intent You can track changes properly Teams can work on the system together Requirements become their own quality checks Changes start from clear specifications The first company to get this will own the next phase of AI development tools. They’ll build tools for real software instead of toys. They’ll make everything available today look like primitive experiments.
More in technology
Here are the Vim forks I know about: Neovim: “a Vim-based text editor engineered for extensibility and usability, to encourage new applications and contributions.” Vim Classic: “a stable, long-term support fork of the ubiquitous text editor, maintained without the assistance of generative AI tools.” EVi: “a hard-fork of Vim v9.1.0 (Jan 2024) before AI was used in the project.” MacVim: “a macOS version of the Vim text editor, providing a graphical user interface for Vim, while tightly integrating with macOS and providing features specific to the platform.” This list doesn’t include Vim-inspired editors like Kakoune, Vim-style plugins for other editors like IdeaVim, or Vim “distributions” like LazyVim. Contact me if you know of other forks!
Thought I’d make a quick blog post about some weird behavior I’m seeing on iOS 27 and Dynamic Island live activities, that I initially thought was restricted to the Duo, but turns out affects everything. Yay. Maybe this post can save some others some time, and hopefully Apple can fix it. Long story short, ever since iOS 16.1 introduced “Live Activities”, where we can display handy little widgets in the Dynamic Island, there’s been a bunch of different configurations for showing things up there: something on the left, the right, when expanded, when two apps are showing things and yours gets a bit smaller, and on the lock screen. One interesting one is if you only need to show a small indicator in the Dynamic Island (screen recording indicator, electric vehicle charging status, etc.) you can do so, which just shows on the left on older iPhones, and on the Duo a single one below the camera cutout. Apple describes how to do this in their developer documentation (emphasis added): If you can’t find a solution to display views for leading and trailing compact presentations with limited width, provide a compact leading presentation that best represents your Live Activity, such as the view you use for the minimal presentation, and an empty view for the trailing compact presentation. The system hides the empty trailing view automatically. Unfortunately, in iOS 27 (and 27.1, and 27.2), this only half works, due to a new addition in iOS 27: isDynamicIslandLimitedInWidth. It’s a variable that basically tells you if the Dynamic Island is even narrower than normal, presumably because they knew the Duo was coming with its vertical Dynamic Island, but also because as of iOS 27, Live Activities now show in the Dynamic Island when the device is in landscape too. So it’s handy to know if you have a bit less space than usual. In this new narrow view however, it seems like they didn’t test all the configurations thoroughly, as if you provide EmptyView for that trailing compact presentation, it’s not empty at all, and in fact enforces a quite large minimum size so the Dynamic Island looks hilariously tall. In other words: The system hides the empty trailing view automatically Left: what it should look like. Right: reality You can imagine this is especially rough on the Duo, where navigation buttons, toolbar buttons, tab bar buttons, etc. are all competing for precious space on the right. But that’s only half true. It displays perfectly fine (hiding it) when isDynamicIslandLimitedInWidth is false (AKA how it always used to be), but in the new cases where it can be true, like the Duo and iPhones in landscape, that’s when it breaks. So tl;dr: In iOS 27, Dynamic Island Live Activities are improperly sized if you use the listed EmptyView configuration and isDynamicIslandLimitedInWidth is true. This makes it especially bad on the Duo, womp womp. Complications Now, you might be saying “That’s weird, I haven’t seen that behavior! This is nothing but hogwash!”. But it is not hogwash, just a little complicated. There’s basically three categories of apps. Apple apps Maps, for instance, displays this single, small Dynamic Island live activity perfectly well and sized, and has been beautifully compiled with the iOS 27.2 SDK. By all accounts it should show this bug, too! Apple apps don’t have to use the same APIs as us normies, so funnily enough through really cool tools like REA we can disassemble the iOS Simulator and see that it’s in fact true, the Apple Maps live activity just completely bypasses WidgetKit and instead just renders plain UIKit through MapsBannerViewController which shows the cute little cyan widget through a ManeuverAccessoryView and then for the hidden trailing view simply returns a DistanceAccessoryView with CGSizeZero, which iOS actually respects, unlike our paltry SwiftUI EmptyView. Third-party apps NOT updated to iOS 27 If your app hasn’t been compiled with any of the iOS 27 SDKs, it only knows about iOS 26, and thus doesn’t know about (or have to worry about) the new, troublesome isDynamicIslandLimitedInWidth property, so it just… displays perfectly well. Here’s the Rivian app, which hasn’t been updated for iOS 27 yet, which shows the Live Activity displaying perfectly sized on the Duo (indicating vehicle charging): (Technical disassembly detail for the nerds: iOS shows the app’s minimal config, a smaller config normally used when multiple apps are in the Dynamic Island together, rather than the normal compact config, so it bypasses this issue entirely.) Developers who updated their apps for iOS 27 Surprise! That picture from before? On the left is just me compiling with the old 26 SDK that works! Unfortunately if you are a good developer who updated their app for iOS 27 and all the new features, you kind of get punished through this bug, because your live activity displays the improperly sized way described above, and you can’t just use raw UIKit like Apple. So you’re kinda stuck where I am: do I ship a weirdly sized Dynamic Island experience for everyone? Do I shove something extraneous in the compactTrailing spot just so it doesn’t look as empty? Do I not update the app at all until Apple fixes this bug? Widgets are techincally a separate extension from your main app, so you could compile them with a different SDK (26) than your main app (27) which fixes this bug, but I’m not sure Apple approves of that. Apple On the bright side hopefully it’s an easy enough fix, treating EmptyView like you used to on iOS 26 and per the documentation (the part that is weirdly right beside the mention of the new API that introduces this craziness in the first place). Radar/feedback assistant ID: FB25301955 Easy reproduction code if you’re curious: import ActivityKit import SwiftUI import WidgetKit struct CircleAttributes: ActivityAttributes { struct ContentState: Codable, Hashable {} } struct BlueCircle: View { var body: some View { Circle() .fill(.blue) .frame(width: 20, height: 20) } } @main struct CircleLiveActivity: Widget { var body: some WidgetConfiguration { ActivityConfiguration(for: CircleAttributes.self) { _ in BlueCircle() } dynamicIsland: { _ in DynamicIsland { DynamicIslandExpandedRegion(.center) { BlueCircle() } } compactLeading: { BlueCircle() } compactTrailing: { EmptyView() // pls respect me } minimal: { BlueCircle() } } } }
Vicky, driving me home from O'Hare, got sixteen dollars for the best conversation I had in months. The last line is hers.
I’ve had “AI usage statements” in the back of my mind ever since I read Ethan Marcott’s blog post sharing a round-up of his favorite ones. I didn’t consider writing one for this site, since it had the smell of status-signaling. Like a quasi-political declaration of allegiance to some sort of in-group (be it pro-AI, anti-AI, etc). I couldn’t be less interested in that.1 But then something happened that caught me completely off-guard. I was reading a post on my friend’s personal blog. This was a guy that I knew personally and looked up to. I’ve been reading everything he had been writing for years. About two-thirds of the way through a long (and fairly insightful) blog post, I had a sudden realization that the post had been written by AI. I’m not the best at detecting this kind of thing, but once I saw it, I couldn’t unsee it. It was unmistakable. And the thing is, I didn’t feel indifferent or even disappointed, as I might have predicted. I felt betrayed. For somebody who considers themselves open-minded—even optimistic—towards the development of AI, I didn’t know where this feeling was coming from. I have theories… maybe it has something to do with the social contract between the writer and the reader. Whatever it is, I didn’t want anyone who was reading my blog (hi mom 👋) to feel the same thing. So for the sake of transparency, here is my AI usage statement: The content of this website is written by me. I do not use AI to generate the text you are reading because writing from scratch helps me process my thoughts (which is one of the main reasons I blog in the first place). I don’t currently use AI-powered writing tools (grammar-check, spell-check, dictation tools, etc), but I may experiment with them as long as they don’t undermine the thinking process. The code for this website was originally hand-written, but I have since used AI to make several useful updates. I expect to continue using AI to write code for this website and other personal coding projects. When I create a public project exclusively with AI, I mention that fact in the project README.2 I am experimenting and my AI usage may change. When it does, I’ll update the policy here on my /about page, which is where it will live long-term. To explain why, see Paul Graham's Keep your Identity Small. ↩ For an example, see s-tier ↩ Thanks for reading this post via RSS! Let me know your thoughts by leaving a comment on the original post or sending me an email.