<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>README — design</title>
<link>https://readme.news/tags/design/</link>
<atom:link href="https://readme.news/tags/design/feed.xml" rel="self" type="application/rss+xml"/>
<description>README pieces tagged design.</description>
<language>en-us</language>
<lastBuildDate>Thu, 01 Oct 2026 13:20:31 +0000</lastBuildDate>
<item><title>The interface is the product</title><link>https://readme.news/the-interface-is-the-product/</link><guid isPermaLink="true">https://readme.news/the-interface-is-the-product/</guid><pubDate>Mon, 31 Aug 2026 09:00:00 +0000</pubDate><description>Users cannot see your architecture. They can see the six seconds it takes to do the thing they came for.</description><content:encoded><![CDATA[<p>Engineers evaluate software by its internals: the architecture, the correctness, the elegance of the data model. Users evaluate it by the sequence of actions required to get what they came for.</p>
<p>These correlate less than we would like, and the gap is where a lot of otherwise good software fails.</p>
<h2 id="what-users-actually-experience">what users actually experience<a class="anchor" href="#what-users-actually-experience" aria-label="link to this section">#</a></h2>
<p>Not your service boundaries. Not your consistency model. Not the fact that you handle a partition correctly.</p>
<ul><li>How many steps to do the common thing.</li><li>Whether it responds instantly or after a spinner.</li><li>Whether an error tells them what to do.</li><li>Whether it does the same thing twice in a row.</li><li>Whether it remembers what they told it last time.</li></ul>
<p>Every one of those is an interface property, and every one is achievable on top of an ugly implementation — or destroyed on top of a beautiful one.</p>
<h2 id="the-trade-that-is-usually-made-backwards">the trade that is usually made backwards<a class="anchor" href="#the-trade-that-is-usually-made-backwards" aria-label="link to this section">#</a></h2>
<p>There is a real tension between internal cleanliness and external simplicity, and it comes up constantly:</p>
<ul><li>The clean data model exposes three concepts where users think in one.</li><li>The correct API makes the caller specify things they do not care about.</li><li>The properly-separated services mean the UI has to make four calls and handle each failing independently.</li><li>The general solution has eleven configuration options; the specific one would have had zero.</li></ul>
<p>The default resolution is to protect the internals and push the complexity outward, because the internals are what engineers look at and defend in review.</p>
<p>That is backwards. <strong>The interface is used far more times than the implementation is read.</strong> Complexity at the boundary is multiplied by every user and every call; complexity inside is paid by the people who chose it.</p>
<h2 id="what-this-looks-like-in-practice">what this looks like in practice<a class="anchor" href="#what-this-looks-like-in-practice" aria-label="link to this section">#</a></h2>
<p><strong>Collapse concepts at the boundary.</strong> If users think of one thing and your model has three, expose one and do the mapping internally. Yes, that is a lossy abstraction and you will occasionally have to break it. That is the correct place to put the pain.</p>
<p><strong>Default everything.</strong> Every required parameter is a decision you have forced on someone who has less context than you. Make it optional with a sensible default, and let the people who genuinely need control find the option.</p>
<p><strong>Make the common path one step.</strong> Count the actions for the thing 90% of users do 90% of the time. If it is more than two, that is the work.</p>
<p><strong>Absorb the failure.</strong> If your architecture means four things can fail independently, the interface should not surface four independent failures. Retry, degrade, or present one coherent state.</p>
<p><strong>Make it fast where they are waiting.</strong> A local read, an optimistic update, a cached response. Users cannot tell the difference between "fast because it is well-engineered" and "fast because you cheated" — and neither can anyone else.</p>
<h2 id="the-counterweight-honestly">the counterweight, honestly<a class="anchor" href="#the-counterweight-honestly" aria-label="link to this section">#</a></h2>
<p>This is not an argument for shipping a nice facade over a broken system. The internals are what make the interface <em>keep</em> working — under load, at the edges, after a year of changes. Software that is lovely to use and impossible to change dies just as reliably, only slower.</p>
<p>The claim is narrower: <strong>when the two genuinely conflict, and they do, the interface should usually win</strong> — because it is what the software is <em>for</em>, and because interface decisions are much more expensive to reverse than internal ones. You can rewrite the storage layer. You cannot easily take back a concept you taught a million users.</p>
<h2 id="the-test">the test<a class="anchor" href="#the-test" aria-label="link to this section">#</a></h2>
<p>Watch someone use your software for the first time without helping them.</p>
<p>Count the moments they hesitate. Each one is a place where your model and theirs diverged, and no amount of internal quality closes that gap.</p>
<p>That exercise is uncomfortable, takes twenty minutes, and consistently produces a better backlog than any amount of architectural discussion.</p>]]></content:encoded></item><item><title>Error messages are a user interface</title><link>https://readme.news/error-messages-are-a-user-interface/</link><guid isPermaLink="true">https://readme.news/error-messages-are-a-user-interface/</guid><pubDate>Wed, 06 May 2026 09:00:00 +0000</pubDate><description>The message someone reads at their worst moment gets less design attention than a button. Here&#x27;s what a good one contains.</description><content:encoded><![CDATA[<p>An error message is read by a person who is already frustrated, under time pressure, in an unfamiliar state. It is the highest-stakes text in your product and it typically receives less design attention than a tooltip.</p>
<h2 id="the-anatomy-of-a-good-one">the anatomy of a good one<a class="anchor" href="#the-anatomy-of-a-good-one" aria-label="link to this section">#</a></h2>
<p>Four parts. Most messages have one.</p>
<p><strong>What happened</strong>, specifically.</p>
<p>Not "an error occurred." What failed, in terms the reader can act on.</p>
<p><strong>Why</strong>, if you know.</p>
<p>The immediate cause. Not the stack trace — the reason.</p>
<p><strong>What to do next.</strong></p>
<p>The single most valuable part and the most often missing. Even "retry in a few minutes" is infinitely better than nothing.</p>
<p><strong>How to get help.</strong></p>
<p>An error code, a request ID, a link. Something the reader can paste into a support conversation that makes the problem findable.</p>
<h2 id="the-transformation">the transformation<a class="anchor" href="#the-transformation" aria-label="link to this section">#</a></h2>
<div class="code"><pre><code>Error: ECONNREFUSED</code></pre></div>
<p>versus</p>
<div class="code"><pre><code>Could not connect to the database at db.internal:5432.

The connection was refused, which usually means the database is not
running or a firewall is blocking the port.

Try:
  1. Check the database is running:  systemctl status postgresql
  2. Verify the host and port in config/database.yml
  3. Check network access:           nc -zv db.internal 5432

If this persists, include request ID a1b2c3d4 when reporting.</code></pre></div>
<p>The second is longer, and length is not the cost people think it is. Nobody has ever complained that an error message told them too much about how to fix their problem.</p>
<h2 id="the-rules">the rules<a class="anchor" href="#the-rules" aria-label="link to this section">#</a></h2>
<p><strong>Never blame the user.</strong> "Invalid input" is accusatory and unhelpful. "Email address must contain an @ — you entered <code>dom.example.com</code>" is neither.</p>
<p><strong>Echo what they gave you.</strong> The single most useful thing a validation message can do is show the value that failed. The user's mental model of what they typed is frequently wrong, and showing it resolves the confusion instantly.</p>
<p><strong>Be specific about which one.</strong> "One or more fields are invalid" makes the user hunt. Name the field. Highlight it. If there are several, list all of them — do not make them fix one, submit, and discover the next.</p>
<p><strong>Distinguish the four failure classes</strong>, because the correct user action is different for each:</p>
<div class="table-wrap"><table><thead><tr><th style="text-align:left">class</th><th style="text-align:left">what the user should do</th></tr></thead><tbody><tr><td style="text-align:left">your fault (bug)</td><td style="text-align:left">report it, with an ID</td></tr><tr><td style="text-align:left">their fault (bad input)</td><td style="text-align:left">fix the input, specifically</td></tr><tr><td style="text-align:left">transient (network, load)</td><td style="text-align:left">retry, after a stated interval</td></tr><tr><td style="text-align:left">policy (not permitted)</td><td style="text-align:left">request access, from a named place</td></tr></tbody></table></div>
<p>Collapsing these into one generic message means the user cannot tell whether to retry, fix something, or give up.</p>
<p><strong>Include an identifier for the unfixable ones.</strong> When it is genuinely your bug, the user cannot fix it and the best thing you can give them is a request ID that appears in your logs. That converts "it's broken" into a support conversation you can actually resolve.</p>
<p><strong>Never expose internals to end users, always expose them to developers.</strong> A stack trace on a public error page is an information disclosure. The same trace in a developer tool's console is essential. Know which audience you are writing for.</p>
<h2 id="the-developer-facing-case">the developer-facing case<a class="anchor" href="#the-developer-facing-case" aria-label="link to this section">#</a></h2>
<p>For errors read by other engineers — library exceptions, CLI failures, API responses — the bar is different and higher.</p>
<p><strong>Say what you expected and what you got.</strong></p>
<div class="code"><pre><code>expected `timeout` to be a positive integer (milliseconds), got: "30s"

Durations must be numeric. Use 30000 instead of "30s".</code></pre></div>
<p><strong>Point at the source.</strong> File, line, and the offending fragment. Compilers have done this well for years and application errors mostly have not.</p>
<p><strong>Link to documentation</strong> with an anchor to the specific section, not the homepage.</p>
<p><strong>Give the error a stable identifier</strong> so people can search for it. <code>E1042</code> is searchable in a way that a prose sentence you will reword next release is not.</p>
<p>Rust's compiler errors remain the standard here and the reason is that someone decided error messages were a feature and staffed them accordingly.</p>
<h2 id="the-audit-worth-running">the audit worth running<a class="anchor" href="#the-audit-worth-running" aria-label="link to this section">#</a></h2>
<p>Grep your codebase for every string that gets shown on a failure. Read them as a list, out of context, the way a user encounters them.</p>
<p>You will find: generic messages, internal jargon, messages that describe the code's state rather than the user's situation, and at least one that says something like "this should never happen."</p>
<p>Fixing them is a day of work and it is the cheapest quality improvement available in most products.</p>
<h2 id="the-framing">the framing<a class="anchor" href="#the-framing" aria-label="link to this section">#</a></h2>
<p>Every error message is a conversation with someone having a bad time. You get one sentence, maybe three, to help.</p>
<p>Write them like you will be the one reading them at 2 a.m., because eventually you will be.</p>]]></content:encoded></item><item><title>Write the README first</title><link>https://readme.news/write-the-readme-first/</link><guid isPermaLink="true">https://readme.news/write-the-readme-first/</guid><pubDate>Sat, 05 Jul 2025 09:00:00 +0000</pubDate><description>The oldest trick in the book, still the best design tool available, and almost nobody does it.</description><content:encoded><![CDATA[<p>Before you write the code, write the README. Not a stub. The real one — the one you would ship, with the install instructions, the first example, and the section explaining what it does not do.</p>
<p>You will throw most of it away. That is fine. The value is in what happens while you write it.</p>
<h2 id="what-it-forces">what it forces<a class="anchor" href="#what-it-forces" aria-label="link to this section">#</a></h2>
<p><strong>A name for the thing.</strong> If you cannot name it in one sentence, you do not know what it is. "A library for X" where X is three clauses joined by "and" is three libraries.</p>
<p><strong>A first example that fits on a screen.</strong> This is the brutal one. Write the code a new user types first. If it needs eleven lines of setup before anything happens, your API is wrong, and you found out before you built it rather than after.</p>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python"># if this is your example, stop and redesign
client = Client(config=Config(
    auth=AuthProvider(strategy=Strategy.OAUTH, ...),
    transport=Transport(pool=Pool(size=10), retry=RetryPolicy(...)),
    serializer=JsonSerializer(...),
))
result = client.get_resource(ResourceRequest(id=ResourceId("x")))</code></pre></div>
<div class="code"><span class="code-lang">python</span><pre><code class="lang-python"># if this is your example, you are probably fine
client = Client(api_key=key)
result = client.get("x")</code></pre></div>
<p><strong>An explicit list of non-goals.</strong> Every project has them and most only discover them in a GitHub issue eighteen months later when someone asks for a feature that would break the design. Writing them down early is the cheapest scope control that exists.</p>
<p><strong>Error cases described in prose.</strong> "If the token is expired, X happens." Writing that sentence often reveals that you have not decided what X is, and the decision is much cheaper now.</p>
<h2 id="why-it-works-better-than-a-design-doc">why it works better than a design doc<a class="anchor" href="#why-it-works-better-than-a-design-doc" aria-label="link to this section">#</a></h2>
<p>Design documents are written for reviewers. READMEs are written for users. The audience difference changes everything.</p>
<p>A design doc rewards completeness, so it grows sections about alternatives considered and migration strategies and rollout phases, and by page six nobody including the author is thinking about the <a class="xref" href="/the-interface-is-the-product/" title="The interface is the product">interface</a> anymore.</p>
<p>A README rewards clarity from the outside. It cannot contain your internal rationale, because users do not care. It can only contain what the thing does and how to use it, which is exactly the part that is expensive to change later.</p>
<h2 id="the-internal-service-version">the internal-service version<a class="anchor" href="#the-internal-service-version" aria-label="link to this section">#</a></h2>
<p>This works for services too, and possibly better.</p>
<p>Write the endpoint documentation before the endpoint. Write the example request and the example response. Write the error codes and what each one means.</p>
<p>Then send it to the team that will consume it — <em>before</em> you build anything. The feedback you get in that thread is worth more than any amount of code review after the fact, because at that point changing the interface is free.</p>
<p>Half the API design mistakes I have watched happen were mistakes that the consuming team would have caught in ninety seconds if anyone had shown them the shape first.</p>
<h2 id="the-objection">the objection<a class="anchor" href="#the-objection" aria-label="link to this section">#</a></h2>
<p>"I do not know what it does yet, that is why I am building it."</p>
<p>Sometimes true. Exploratory work where the shape is genuinely unknown should be exploratory, and forcing a README onto it is cargo cult.</p>
<p>But be honest about which mode you are in. Most of the time you do know roughly what it does, and "I will figure out the interface as I go" means "I will discover the interface from whatever was convenient to implement," which is how you end up with a function that takes eleven positional booleans.</p>
<h2 id="the-actual-test">the actual test<a class="anchor" href="#the-actual-test" aria-label="link to this section">#</a></h2>
<p>When you finish, read the README as if you had never seen the project.</p>
<p>If your reaction is "I do not understand what problem this solves," you have learned something enormously valuable for the cost of twenty minutes and no code.</p>
<p>That is the best exchange rate in software.</p>]]></content:encoded></item><item><title>WWDC 2025: Liquid Glass, a version number reset, and a local model API</title><link>https://readme.news/wwdc-2025-liquid-glass-a-version-number-reset-and-a-local-model-api/</link><guid isPermaLink="true">https://readme.news/wwdc-2025-liquid-glass-a-version-number-reset-and-a-local-model-api/</guid><pubDate>Tue, 10 Jun 2025 09:00:00 +0000</pubDate><description>Apple renumbers every OS to 26, redesigns everything, and quietly ships the most developer-relevant thing in years.</description><content:encoded><![CDATA[<p>Apple's developer conference delivered a visual redesign, a version-numbering change, and an API that matters more than either.</p>
<h2 id="the-version-reset">the version reset<a class="anchor" href="#the-version-reset" aria-label="link to this section">#</a></h2>
<p>Every OS jumps to 26: iOS 26, macOS 26 Tahoe, watchOS 26, tvOS 26, visionOS 26. Year-based, aligned across platforms, matching the model-year convention.</p>
<p>Genuinely good housekeeping. "Requires iOS 17, macOS 14, watchOS 10" was needlessly hard to reason about. Now it is one number.</p>
<h2 id="liquid-glass">Liquid Glass<a class="anchor" href="#liquid-glass" aria-label="link to this section">#</a></h2>
<p>A system-wide redesign built around translucent, refractive material that reacts to content behind and beneath it. Controls float. Layers have depth. Things bend light.</p>
<p>Reactions split predictably. It is undeniably a strong visual identity and the first genuinely new direction since iOS 7 flattened everything in 2013. It is also, in the first betas, a legibility problem in a lot of contexts — text over a refractive layer over a busy background is exactly the situation typography guidance has warned about for a century.</p>
<p>Apple will iterate through the beta cycle. They always do. Contrast will be raised, blur will be increased, and the shipping version will be about 70% of the demo. That is the normal arc and knowing it saves you from having the argument twice.</p>
<p>For developers: if you use standard controls you get it for free. If you built custom UI, budget real time. Custom navigation bars and tab bars in particular are going to need work.</p>
<h2 id="foundation-models-framework">Foundation Models framework<a class="anchor" href="#foundation-models-framework" aria-label="link to this section">#</a></h2>
<p>Here is the actual news. Apple exposes the on-device model to <a class="xref" href="/pixel-10-and-the-on-device-model-as-a-platform-feature/" title="Pixel 10 and the on-device model as a platform feature">third-party apps</a> via a Swift API, with guided generation, tool calling, and streaming.</p>
<div class="code"><span class="code-lang">swift</span><pre><code class="lang-swift">import FoundationModels

@Generable
struct Recipe {
    @Guide(description: "Dish name") var name: String
    @Guide(.count(3...8)) var ingredients: [String]
    var minutes: Int
}

let session = LanguageModelSession()
let recipe = try await session.respond(to: "A quick pasta dish", generating: Recipe.self)</code></pre></div>
<p>That <code>@Generable</code> macro is the good part. You define a Swift type, the framework constrains decoding so the output is guaranteed to parse into it. No JSON parsing, no retry loop for malformed output, no schema drift between your prompt and your struct. Type safety all the way through.</p>
<p>And it costs nothing per call. No API key, no rate limit, no network, no privacy review. For a small on-device model that is enough for summarization, classification, extraction, and simple generation, that changes the calculus for a huge number of app features that were previously not worth a server bill.</p>
<p>The model is small — roughly 3B parameters — and you should not expect frontier behavior. Expect a good <a class="xref" href="/haiku-45-and-the-collapsing-cost-of-good-enough/" title="Haiku 4.5 and the collapsing cost of good-enough">small model</a> that is free and private, and design features that fit that envelope.</p>
<h2 id="containerization">Containerization<a class="anchor" href="#containerization" aria-label="link to this section">#</a></h2>
<p>A framework for running Linux containers on macOS with each container in its own lightweight VM, open source, with sub-second start times. Docker Desktop on macOS has been a performance complaint for a decade. This is Apple's answer and it is architecturally cleaner: per-container VMs rather than one shared Linux VM.</p>
<h2 id="xcode-26">Xcode 26<a class="anchor" href="#xcode-26" aria-label="link to this section">#</a></h2>
<p>Model integration in the editor with support for multiple providers including Claude, plus a new coding assistant experience. Apple shipping first-party support for a competitor's model inside its own IDE is notable — it means they have concluded the model layer is a component, not a differentiator.</p>
<h2 id="the-read">the read<a class="anchor" href="#the-read" aria-label="link to this section">#</a></h2>
<p>Consumer-facing, this was a design conference. Developer-facing, it was the conference where Apple's <a class="xref" href="/local-first-is-finally-practical/" title="Local-first is finally practical">local-first</a> AI strategy finally produced something you can build on.</p>
<p>The strategy is coherent: small models on device, free and private, with the big stuff handled elsewhere. It is not going to win benchmark comparisons and it was never trying to.</p>]]></content:encoded></item><item><title>OpenAI buys a hardware company that hasn't shipped anything</title><link>https://readme.news/openai-buys-a-hardware-company-that-hasnt-shipped-anything/</link><guid isPermaLink="true">https://readme.news/openai-buys-a-hardware-company-that-hasnt-shipped-anything/</guid><pubDate>Tue, 27 May 2025 09:00:00 +0000</pubDate><description>$6.5 billion for io, Jony Ive&#x27;s design studio. The bet is that the phone is the wrong shape for this.</description><content:encoded><![CDATA[<p>OpenAI announced an all-stock acquisition of io, the hardware startup founded by Jony Ive, valued at around $6.5 billion. No product exists. The first device is described as arriving in 2026.</p>
<p>Six and a half billion dollars for a design team and an idea is a lot of money, and it is worth taking the underlying thesis seriously even if you think the price is absurd.</p>
<h2 id="the-thesis">the thesis<a class="anchor" href="#the-thesis" aria-label="link to this section">#</a></h2>
<p>The smartphone is optimized for an interaction model that AI makes obsolete.</p>
<p>A phone is a rectangle of apps. You unlock it, find the app, navigate its hierarchy, and perform a task. That design solved the problem of "how do I access many different services on a small screen," and it solved it well enough that the form factor has been essentially static for fifteen years.</p>
<p>If the interaction model becomes "state your intent, the system figures out which services to use," then most of the phone's design — the grid, the navigation, the visual hierarchy — is scaffolding for a problem that no longer exists. What you need instead is something always available, ambient, primarily audio, with a screen only when a screen is genuinely the right modality.</p>
<p>That is a coherent thesis. It is also the thesis behind two products that failed badly and publicly in 2024: the Humane Ai Pin and the Rabbit R1.</p>
<h2 id="why-those-failed-and-whether-this-is-different">why those failed and whether this is different<a class="anchor" href="#why-those-failed-and-whether-this-is-different" aria-label="link to this section">#</a></h2>
<p>The Ai Pin and R1 failed for the same three reasons:</p>
<ol><li><strong>The models were not good enough.</strong> Both shipped in early 2024, before reliable tool use, before good latency, before models that could handle ambiguity gracefully. The demos were staged; the products were not ready.</li><li><strong>The hardware was bad.</strong> Thermal problems, terrible battery life, laser projection nobody could read in daylight.</li><li><strong>They competed with a phone that was in the same pocket.</strong> Any task the device could not do, the phone could. That is a brutal comparison to survive.</li></ol>
<p>Reason one has changed a lot in eighteen months and will change more by 2026. Reason two is precisely what you buy Jony Ive's team for. Reason three has not changed at all and is, I think, the actual problem.</p>
<h2 id="the-part-that-is-genuinely-hard">the part that is genuinely hard<a class="anchor" href="#the-part-that-is-genuinely-hard" aria-label="link to this section">#</a></h2>
<p>A dedicated AI device has to be better than a phone at <em>something</em> to justify existing. The candidates:</p>
<ul><li><strong>Always-on context.</strong> A device that sees and hears what you do all day has context a phone does not. That is also the most invasive product concept in consumer electronics history, and the social norms around it do not exist.</li><li><strong>Zero-friction capture.</strong> Say something, it is recorded, transcribed, actioned. Genuinely better than unlocking a phone. Also achievable by a watch or earbuds, which people already wear.</li><li><strong>Not being a phone.</strong> There is a real and growing market for devices that do not have a feed. That market is not obviously large enough for a $6.5B bet.</li></ul>
<h2 id="the-strategic-read">the strategic read<a class="anchor" href="#the-strategic-read" aria-label="link to this section">#</a></h2>
<p>OpenAI's dependence on Apple and Google for distribution is a structural risk. Every ChatGPT interaction on a phone happens on an operating system built by a competitor who can change the rules. Owning hardware is the only permanent solution to that, and it is worth a lot to not be a tenant.</p>
<p>Whether it is worth this much, executed by a team that has never shipped hardware at this company, on a two-year timeline, in a category with a hundred percent failure rate so far — that is a different question.</p>
<p>I would not bet against Ive on the object. I would bet against the category on the timeline.</p>]]></content:encoded></item>
</channel>
</rss>
