<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Simple Memo</title>
    <description>The latest articles on DEV Community by Simple Memo (@simple_memo).</description>
    <link>https://dev.to/simple_memo</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3919840%2Ff3e34759-885a-4e5e-9959-57c82a1a9c45.png</url>
      <title>DEV Community: Simple Memo</title>
      <link>https://dev.to/simple_memo</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/simple_memo"/>
    <language>en</language>
    <item>
      <title>How I chose a note format future-me and an LLM can both read</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 18 Aug 2026 13:14:59 +0000</pubDate>
      <link>https://dev.to/simple_memo/how-i-chose-a-note-format-future-me-and-an-llm-can-both-read-46d9</link>
      <guid>https://dev.to/simple_memo/how-i-chose-a-note-format-future-me-and-an-llm-can-both-read-46d9</guid>
      <description>&lt;p&gt;I am not going to tell you which note format I settled on until the end. I want to show you how I picked it, because the decision is reusable and the answer probably isn't. I have kept notes as a solo developer for about three years, and for most of that time they had exactly one reader: a slightly tired version of me, three weeks later, trying to remember why I changed something. Then the second reader showed up. Now I paste my own notes into Claude and Cursor as context two or three times a week, and the model reads them cold, with no idea what any of my private shorthand meant. The day I started doing that, some of my formats quietly fell apart.&lt;/p&gt;

&lt;p&gt;So I did the boring thing and ran an actual decision instead of drifting into a habit. Here is the whole thing, axes and weights and rejected options included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two constraints, ranked before I looked at any format:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Capture friction comes first. A format I won't use at the end of a bad day is worth nothing, no matter how elegant it reads.&lt;/li&gt;
&lt;li&gt;Machine-legibility comes second. The notes have to survive being pasted into a model with zero cleanup, or the second reader gets nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The five formats I actually weighed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Freeform prose.&lt;/strong&gt; Just write paragraphs. Lowest possible friction going in: you type what you think. The cost shows up later. Future-me can't skim a wall of prose, and a model handed three months of undated paragraphs struggles to tell where one thought ends and the next begins. It reads everything as one blurry context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-note structured metadata.&lt;/strong&gt; Every note gets a small header: a title field, tags, a type, a date, maybe a status. This is lovely to query and genuinely nice for a machine, which likes named fields. But the friction is brutal at exactly the wrong moment. To write one line about a bug, I first have to decide its title and its type and its tags. Most nights I just wouldn't, and an unwritten note beats a well-formatted one zero times out of a hundred.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full Zettelkasten.&lt;/strong&gt; Atomic notes, stable IDs, dense links between them. In theory the richest structure of the five, and the only one that builds a real graph. In practice it asks me to do link-maintenance forever, and I already ran this experiment and quit it. I hand-kept around 1,400 Luhmann-style IDs for three years and then deleted every one of them, and my notes got better, not worse. That result is still fresh, so Zettelkasten started this round with a scar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timestamped one-liners.&lt;/strong&gt; Plain text, one thought per line, each line beginning with a date and a concrete noun or verb. &lt;code&gt;2026-03-14 fixed cold-start regression: moved the keychain read off the launch path&lt;/code&gt;. No header, no fields, no links. It looks almost too simple to count as a system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question-and-answer notes.&lt;/strong&gt; Each note is a question with an answer underneath, like a private FAQ. Wonderful for the notes that are genuinely questions. Terrible ceremony for the eighty percent that are just an observation I want to park before it evaporates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which axes actually decide it?
&lt;/h2&gt;

&lt;p&gt;I scored the five on the five things I care about. Low, medium, high — where for maintenance cost, low is good.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Format&lt;/th&gt;
&lt;th&gt;Capture friction&lt;/th&gt;
&lt;th&gt;Machine-legibility&lt;/th&gt;
&lt;th&gt;Future-me skimming&lt;/th&gt;
&lt;th&gt;Graph / link value&lt;/th&gt;
&lt;th&gt;Maintenance cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Freeform prose&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Structured metadata&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zettelkasten&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Timestamped one-liners&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Question-and-answer&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table alone doesn't pick a winner, because the columns are not worth the same. A format that scores high on graph value and high on friction is not a tie against one that scores low on both — it depends entirely on how much I weight friction. Which is the actual decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I weighted the axes
&lt;/h2&gt;

&lt;p&gt;I gave capture friction more weight than everything else combined. This is not a principled information-theory stance. It is a fact about me: I am one person, I capture at the worst moments, and any friction at capture time doesn't make my notes tidier, it makes them not exist. So friction dominates.&lt;/p&gt;

&lt;p&gt;Machine-legibility came second and it is the axis that changed since last year. A note is now something a model reads, and models turn out to have a clear preference that surprised me: they do not need my metadata nearly as much as they need my ordering and my verbs. A timestamp gives a model sequence for free. A leading verb gives it the topic of the line without a tags field. Plain dated lines read to a model almost like a commit log, and models are very good at commit logs.&lt;/p&gt;

&lt;p&gt;Future-me skimming came third, maintenance cost fourth as a penalty, and graph value came dead last. That last one is the deliberate, uncomfortable trade I want to name out loud: I am choosing a format that will never build me a navigable web of linked ideas. I am giving up the graph on purpose. I decided I almost never actually traverse a knowledge graph; what I actually do is search for a line and read the twenty lines around it. So I optimized for the thing I do, not the thing I once imagined I'd do.&lt;/p&gt;

&lt;p&gt;A concrete example of the weighting doing real work. Structured metadata scored high on machine-legibility and I was tempted, because a model does love a clean &lt;code&gt;type: bug, tags: [ios, launch]&lt;/code&gt; header. But run it through the weights and it collapses: the header buys a little legibility I mostly already get from the verb, and it costs friction on the one axis I refuse to spend on. A format can be objectively richer and still lose, because the axis it wins is the axis I priced cheap and the axis it loses is the one I priced above everything. Writing the weights down first is what stopped me from being seduced by the format that demos best.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does the machine really change the answer?
&lt;/h2&gt;

&lt;p&gt;It is worth being honest about how much the second reader actually moved the decision, because it would be easy to overstate it. Three years ago, with only future-me reading, I would probably have landed on timestamped lines anyway — skimmability and low friction were already pulling that way. What the model added was a second, independent vote for the same format, and it retired one candidate I might otherwise have kept flirting with. I used to assume that the day notes became machine-input, I'd need more structure, not less. The opposite happened. The model reads my least-structured format the most comfortably, because plain dated lines look like exactly the kind of sequential, terse log that these systems were trained on oceans of. The machine didn't ask me to build it a schema. It asked me to stop building one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually picked
&lt;/h2&gt;

&lt;p&gt;Timestamped one-liners. Plain text, one thought per line, each line led by a date and a concrete noun or verb, no per-note structure at all. The lines accumulate in a folder of dated markdown files, and that folder is the whole system. There is no schema to remember and nothing to maintain.&lt;/p&gt;

&lt;p&gt;It won capture friction outright, which was most of the weight, because writing a note is just typing a line. It won machine-legibility for a reason I didn't predict before I tested it: when I paste a month of these lines into a model, I paste them raw, no cleanup, and the model handles them better than it handled my old structured notes, because the timestamps give it order and the leading verbs give it topics. It won future-me skimming because a column of dated first-words is the most skimmable thing I've ever kept. It lost graph value completely, and I let it, because that was the trade I'd already decided to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did the other four lose?
&lt;/h2&gt;

&lt;p&gt;Structured metadata lost on the only axis I refused to compromise: it taxes the moment of capture, and taxed capture is skipped capture. Zettelkasten lost because I had already paid its maintenance cost for three years and &lt;a href="https://dev.to/simple_memo/i-deleted-every-zettelkasten-id-and-my-notes-got-better-fkd"&gt;written about deleting every one of those IDs&lt;/a&gt;; choosing it again would have meant un-learning my own result. Question-and-answer lost because it's a great format wearing the wrong uniform for daily use — I kept it, but only as a thing I reach for deliberately, not as the default. Freeform prose lost on both readers at once: unskimmable for me, unsegmentable for the model.&lt;/p&gt;

&lt;p&gt;None of these are bad formats. They lost against my specific weights. Someone who traverses a real graph every day, or who queries structured fields constantly, should weight the axes differently and would correctly pick something else. That's the point of showing the weights instead of just the answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checks I'll run in three months
&lt;/h2&gt;

&lt;p&gt;A decision journal is worthless if you never check it, so here is how I'll know I chose wrong. Did I stop capturing? If the friction of even a dated line turns out to be too much on bad days, the format failed at its one job. Did a paste into a model need cleanup? If I find myself reformatting notes before they're useful as context, machine-legibility wasn't as high as I scored it. And did I ever badly want a link I didn't have? If I keep reaching for a connection between two lines and the flat format can't give it to me, then I underweighted graph value and I owe myself a rematch.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I had to answer first
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Won't a model prefer clean structured data over plain lines?&lt;/strong&gt; I assumed yes and was wrong in practice. For long-context pasting, ordering and short topical verbs did more work than named fields, and they cost me nothing to produce. A model reading dated lines reconstructs the structure it needs; it does not need me to pre-chew it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isn't plain text going to rot without a system?&lt;/strong&gt; It would if it had no anchor. The timestamp is the system. Every line is findable by date and by its first word, which is all the retrieval I've ever actually performed on my own notes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about tags?&lt;/strong&gt; I don't add them at capture time, because that's the friction I'm avoiding. When I need a category, I search for the verb. The leading word is a tag that costs nothing to write.&lt;/p&gt;

&lt;p&gt;If you keep notes for both a human and a machine now, I'd like to know: what is the one piece of structure you add at capture time that has actually paid off later when you fed the notes to a model — and what did you add once, feel clever about, and end up ripping out?&lt;/p&gt;




&lt;p&gt;I build a small iOS app by myself, and most of it is an argument that the moment of capture should cost almost nothing. I write here every few days about the parts of doing that alone that I'm still getting wrong. If you're curious where these dated lines eventually land, &lt;a href="https://simplememofast.com/obsidian/" rel="noopener noreferrer"&gt;it's here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>watercooler</category>
      <category>genai</category>
      <category>notes</category>
    </item>
    <item>
      <title>I hate marketing. Here are six things I did anyway.</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Fri, 14 Aug 2026 13:09:00 +0000</pubDate>
      <link>https://dev.to/simple_memo/i-hate-marketing-here-are-six-things-i-did-anyway-1ohc</link>
      <guid>https://dev.to/simple_memo/i-hate-marketing-here-are-six-things-i-did-anyway-1ohc</guid>
      <description>&lt;p&gt;Six short pieces from my notebook this month, all about marketing. I have shipped a small iOS app by myself for about fourteen months, and marketing is still the job I schedule last and finish worst. These are not tactics. They are the notes I wrote to myself after each attempt, so that next-me would stop making the same face at the word.&lt;/p&gt;

&lt;h2&gt;
  
  
  The word is the problem, not the work
&lt;/h2&gt;

&lt;p&gt;I kept freezing on the task called "marketing." So I renamed it. Now my calendar says "tell one honest person one true thing," and I do it without the dread.&lt;/p&gt;

&lt;p&gt;The rename mattered more than any tactic. "Marketing" carries a picture: a funnel, a growth curve, someone louder than me winning. "Tell one person one true thing" carries a different picture, which is just a message I already know how to write. Same activity, on most days. The difference is that one of the two framings makes me open the laptop.&lt;/p&gt;

&lt;p&gt;I think the aversion a lot of builders feel is not to the act but to the costume. We imagine we have to become a person who says "growth hack" without wincing. We don't. The work underneath is closer to writing a good commit message: say what changed, say why it matters, keep it short. I can do that. I could never do the costume.&lt;/p&gt;

&lt;h2&gt;
  
  
  What counts as marketing when you hate the word?
&lt;/h2&gt;

&lt;p&gt;For a week I logged everything that touched a stranger's awareness of the app, and then sorted it by whether it felt like "marketing" or not. The results embarrassed me.&lt;/p&gt;

&lt;p&gt;Answering a question in a forum, honestly, without linking anything: felt like being a person, counted as marketing. Fixing a confusing sentence in the App Store description: felt like editing, counted as marketing. Writing a plain post about a bug I caused: felt like confession, was probably my best marketing all month. Meanwhile the thing that felt the most like Marketing, a scheduled promotional post, did approximately nothing.&lt;/p&gt;

&lt;p&gt;The lesson I wrote down: the parts that work rarely feel like marketing, and the parts that feel like marketing rarely work. If a task makes me feel like a salesperson, that is weak evidence it is worth doing. If a task just makes me feel useful to one specific human, that is where the signal has been.&lt;/p&gt;

&lt;h2&gt;
  
  
  The launch is a coin, the habit is a wage
&lt;/h2&gt;

&lt;p&gt;I have launched loudly and I have shown up quietly, and I finally sat down to compare what each actually returns.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;The loud launch&lt;/th&gt;
&lt;th&gt;The quiet habit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Effort shape&lt;/td&gt;
&lt;td&gt;One big spike, then nothing&lt;/td&gt;
&lt;td&gt;Small, daily, boring&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time to feedback&lt;/td&gt;
&lt;td&gt;Same day, then silence&lt;/td&gt;
&lt;td&gt;Slow, then compounding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Who it reaches&lt;/td&gt;
&lt;td&gt;People who like launches&lt;/td&gt;
&lt;td&gt;People who have the problem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How it feels&lt;/td&gt;
&lt;td&gt;Exposed&lt;/td&gt;
&lt;td&gt;Almost invisible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it leaves behind&lt;/td&gt;
&lt;td&gt;A spike in a chart&lt;/td&gt;
&lt;td&gt;A searchable trail&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The loud launch is a coin flip: sometimes it lands, mostly it doesn't, and either way you learn little because the crowd that shows up for launches is not the crowd that keeps a tool. The quiet habit is a wage. It pays badly per hour and reliably over months, because each honest post or answer sits there and keeps getting found. I have stopped hoping for the coin. I show up for the wage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Say the boring true thing before the clever one
&lt;/h2&gt;

&lt;p&gt;My best-performing sentence this year was not clever. It was: "It sends the note as an email and there is nowhere to file it." That is the whole product, stated flatly.&lt;/p&gt;

&lt;p&gt;I spent months trying to find a clever line, an angle, a hook that made a note-to-email app sound like a movement. Every clever line I wrote made the thing sound like more than it is, and people who arrived on a clever line left disappointed by the honest reality. The boring true sentence did the opposite. It set an expectation the app could actually meet, so the few people it drew in stayed.&lt;/p&gt;

&lt;p&gt;I think clever marketing is a tax you pay later, in refunds and churn and one-star reviews that all say "not what I expected." Boring-and-true is the cheaper long game. Describe the thing so plainly that the only people who come are the ones it will actually fit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Does talking about the work count as marketing?
&lt;/h2&gt;

&lt;p&gt;I was not sure, so I ran a small test: for a month I wrote publicly about the mechanics of building alone, with no pitch attached, and I watched what happened to the app I never mentioned by name.&lt;/p&gt;

&lt;p&gt;What happened was quiet and real. A handful of people found the app by following the trail of posts back to the person who wrote them. Nobody bought because of a call to action, because there wasn't one. They bought because they had read enough of my thinking to trust that the tool was built by someone who thought about the problem the way they did. I wrote more about that trade-off in &lt;a href="https://dev.to/simple_memo/five-things-shipping-a-product-alone-took-from-me-3ce0"&gt;an earlier note on what building solo takes from you&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So yes, talking about the work is marketing, and it is the kind I can sustain, because it does not require me to sell. It only requires me to be useful in public and let people draw their own line back to what I make.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I still get wrong
&lt;/h2&gt;

&lt;p&gt;I under-market, and I know it, and knowing it has not fixed it. I will build a feature for three weeks and mention it once, quietly, on a Tuesday, and then feel obscurely virtuous about not being pushy.&lt;/p&gt;

&lt;p&gt;That is not humility. It is a comfortable way to avoid the discomfort of repetition. The honest truth is that most people who would like the thing have not heard of it, will not hear of it once, and will not remember it the first time even if they do. Marketing that hates repeating itself is just a hobby that produces posts. I am trying to say the same true things more than once without feeling like a fraud, and I am not there yet. If you have made peace with repeating yourself, I would like to know how.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I had to answer for myself
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do I need a big audience for any of this to work?&lt;/strong&gt; No. Every good outcome I traced this month came from one person reading one thing, not from reach. A small trail that the right person can follow beat a big spike every time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isn't "just be honest" naive advice?&lt;/strong&gt; It would be if honest were easy. It is not. Saying the plain, unflattering truth about a small product takes more nerve than saying something clever, which is exactly why so little marketing does it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What would I do first if I were starting over?&lt;/strong&gt; Write the one boring true sentence that describes the product, put it everywhere, and stop reaching for the clever version. Then answer questions in public until people know my face.&lt;/p&gt;

&lt;p&gt;I will keep writing these next month. There is a seventh piece already forming, about the difference between being visible and being findable, but it is not done thinking yet.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a solo developer, and Simple Memo is the little iOS thing I make and, it turns out, have to market: one tap turns a thought into an email you have already sent. I write here every few days about the awkward parts of doing every job alone. It &lt;a href="https://apps.apple.com/us/app/captio-style-simple-memo/id6758438948" rel="noopener noreferrer"&gt;lives here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>indiehackers</category>
      <category>marketing</category>
      <category>solodev</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>The worst time to organize a note is the moment you take it</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 11 Aug 2026 13:29:33 +0000</pubDate>
      <link>https://dev.to/simple_memo/the-worst-time-to-organize-a-note-is-the-moment-you-take-it-1fi9</link>
      <guid>https://dev.to/simple_memo/the-worst-time-to-organize-a-note-is-the-moment-you-take-it-1fi9</guid>
      <description>&lt;p&gt;I watched myself do it again last Tuesday. I caught a fix worth remembering, opened a note, and then spent the next ninety seconds deciding which folder it belonged in, what to tag it, and whether it was really a "task" or a "reference." Having the thought took about one second. Filing it took ninety. By the time I was done, the fix I had actually wanted to write down had gone soft at the edges, and I was no longer sure I had it right.&lt;/p&gt;

&lt;p&gt;That ninety seconds is the thing I want to argue about. Not because it is long, but because of &lt;em&gt;when&lt;/em&gt; it lands: at the exact instant I can least afford it.&lt;/p&gt;

&lt;p&gt;Here is the claim, and I will spend the rest of the post earning it. Capture and curation are two different jobs with opposite requirements, and almost every note tool I have used quietly asks me to run both at once. That is the mistake. Do them together and you get the worst of each: capture slow enough that thoughts slip away, and curation done in a one-second window with none of the context that makes it worth doing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the tools actually ask of you
&lt;/h2&gt;

&lt;p&gt;Open most capture flows and count the decisions before the thought is safe. Pick a notebook. Choose a project. Add tags. Set a type. Maybe a due date. Each of those is defensible on its own. Together they turn "write this down" into a small triage meeting, and they schedule that meeting for the one moment you are guaranteed to be busy: mid-thought, mid-task, mid-something.&lt;/p&gt;

&lt;p&gt;The thing the apps get wrong is that they treat the tags and the folders as &lt;em&gt;free&lt;/em&gt;, as if slotting a note into structure were part of writing it. It is not. It is a second, heavier job wearing the first job's clothes. And it is a job you are catastrophically bad at right now, because you are holding a thought in working memory and every keystroke spent on metadata is a keystroke not spent holding it.&lt;/p&gt;

&lt;p&gt;I have measured the cost of interruptions like this on myself before, and it is not linear. A &lt;a href="https://dev.to/simple_memo/a-15-second-interruption-cost-me-40-minutes-of-re-entry-4b5f"&gt;fifteen-second interruption that cost me forty minutes of re-entry&lt;/a&gt; taught me that the price of a context switch is not the switch, it is the climb back. Organizing at capture time is a context switch you volunteer for, on purpose, every single time you have an idea.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does doing both at once cost double?
&lt;/h2&gt;

&lt;p&gt;Because you are running curation blind.&lt;/p&gt;

&lt;p&gt;Curation is the act of deciding where a thought belongs and what it connects to. That decision is only good when you have information: what else is in the vault, what this relates to, whether it is still true next week. At capture time you have exactly none of that. You are deciding a note's permanent address at the one moment you know the least about it. So you either guess, and build a structure full of confident wrong guesses, or you stall for ninety seconds trying to guess well, and lose the thought you came to save.&lt;/p&gt;

&lt;p&gt;There is a reason &lt;a href="https://dev.to/simple_memo/a-thought-you-cant-capture-in-a-second-is-already-gone-20n9"&gt;a thought you cannot capture in about a second is already gone&lt;/a&gt;. The window is that short. Anything you bolt onto the front of capture is spending a budget that was never yours to spend.&lt;/p&gt;

&lt;p&gt;So the tax is double. You pay it once in lost thoughts, because capture got slow. You pay it again in a junk taxonomy, because curation got done at the worst possible time by the least informed version of you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The split I actually run
&lt;/h2&gt;

&lt;p&gt;Two jobs, two modes, and I keep them physically apart so I cannot cheat.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Capture&lt;/th&gt;
&lt;th&gt;Curation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;When&lt;/td&gt;
&lt;td&gt;The instant a thought lands&lt;/td&gt;
&lt;td&gt;Once a week, deliberately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where&lt;/td&gt;
&lt;td&gt;Phone, wherever I am&lt;/td&gt;
&lt;td&gt;Laptop, at a desk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Time budget&lt;/td&gt;
&lt;td&gt;About one second&lt;/td&gt;
&lt;td&gt;Ten to fifteen quiet minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mindset&lt;/td&gt;
&lt;td&gt;Dumb, fast, no judgment&lt;/td&gt;
&lt;td&gt;Slow, full of judgment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Allowed actions&lt;/td&gt;
&lt;td&gt;One line, append it, done&lt;/td&gt;
&lt;td&gt;Read, connect, rename, discard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What kills it&lt;/td&gt;
&lt;td&gt;Any decision at all&lt;/td&gt;
&lt;td&gt;Skipping it for a month&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Capture is append-only. One line, one action, timestamped by the world instead of by me, no folder, no tag, no type. The whole point of the tool I build for myself is that it opens in about a quarter of a second and turns a line into a saved, sent record before I have finished the thought. There is no place to put a tag because I removed the place to put a tag. That absence is a feature.&lt;/p&gt;

&lt;p&gt;Curation is a separate act I do later, on a different device, on purpose. Once a week I sit down at the laptop, read the week's raw lines in a plain markdown vault, and decide what graduates: what becomes its own note, what links to what, what was noise and gets deleted without ceremony. Now I have context. Now the judgment is worth something, because I am reading a thought next to the six thoughts that arrived after it.&lt;/p&gt;

&lt;p&gt;The device split does real work. If capture and curation lived in the same app on the same screen, I would drift into tidying at capture time out of sheer proximity. Making curation happen somewhere else raises the friction just enough that I stop trying to do it in the checkout line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this breaks, and who should ignore me
&lt;/h2&gt;

&lt;p&gt;I owe you the cases where I am wrong, because a rant that only flatters its own thesis is just a mood.&lt;/p&gt;

&lt;p&gt;Dumb capture wins for a solo dev with a curation habit. It stops winning the moment your inbox is shared. If an untriaged item blocks a teammate, then "I'll sort it Sunday" is not discipline, it is a dropped handoff, and some structure at capture time is the price of not being the bottleneck. Team queues and personal inboxes are different animals, and I am only defending the personal one.&lt;/p&gt;

&lt;p&gt;It also breaks if you never actually curate. An append-only inbox you never revisit is not a system, it is a landfill with timestamps. The whole split is load-bearing on the back half. If you know yourself well enough to know the Sunday pass will not happen, then a little structure at capture time is a hedge against your own neglect, and that is a fair trade. I am not making it, but I see it.&lt;/p&gt;

&lt;p&gt;And there is a volume line somewhere. Capture a handful of lines a day and an unstructured inbox stays scannable. Capture two hundred and it does not, and you will want &lt;em&gt;some&lt;/em&gt; coarse bucket at the door just to keep the Sunday pass finishable. I have not hit that line as a team of one. If you have, your mileage is honestly different, not wrongly different.&lt;/p&gt;

&lt;h2&gt;
  
  
  A couple of questions I get
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't a dumb inbox just inbox-zero with extra steps?&lt;/strong&gt; No, and the difference is the timestamp. Inbox-zero is a pressure to &lt;em&gt;clear&lt;/em&gt;. The dated line has no pressure at all, because nothing is overdue and nothing is unfiled, it is just a log. I am not racing a counter to zero. I am reading a week.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if the one-second capture saves garbage?&lt;/strong&gt; It saves some, and I delete it on Sunday in about the time it would have taken to tag it on Tuesday. The asymmetry is the point: deletion is cheap and late and well-informed; tagging is expensive and early and blind. I would rather throw out ten cheap lines than misfile one expensive one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one line I want from you
&lt;/h2&gt;

&lt;p&gt;I am curious where other people draw the exact border, because I suspect mine is slightly wrong and I cannot see which side.&lt;/p&gt;

&lt;p&gt;So, concretely: what is the &lt;em&gt;one&lt;/em&gt; thing you let yourself do to a note at the moment you capture it, and what is the one thing you had to forbid yourself from doing because it kept eating the thought? A single allowed move and a single banned one. I want to see where the line lands for work that isn't mine.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Simple Memo is an iOS app I build by myself: type one line and it is a sent email before your thumb leaves the screen, with nowhere to file it and nothing to tag. I post here every few days about the mechanics of doing this solo. You can find it &lt;a href="https://apps.apple.com/us/app/captio-style-simple-memo/id6758438948" rel="noopener noreferrer"&gt;on the App Store&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>writing</category>
      <category>notes</category>
      <category>devjournal</category>
    </item>
    <item>
      <title>The iOS sandbox turns 18: the ways out, oldest first</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 04 Aug 2026 13:31:24 +0000</pubDate>
      <link>https://dev.to/simple_memo/the-ios-sandbox-turns-18-the-ways-out-oldest-first-4a6f</link>
      <guid>https://dev.to/simple_memo/the-ios-sandbox-turns-18-the-ways-out-oldest-first-4a6f</guid>
      <description>&lt;p&gt;2008: the App Store opens, and every app you install is handed a sealed container it cannot reach out of. Eighteen years later, I still spend real time choosing the right sanctioned way to move one line of text between two iOS apps. This is that history, told oldest method first, by someone who has shipped most of it.&lt;/p&gt;

&lt;p&gt;I build a small iOS app on my own, and its entire job is to get a thought you just typed out of itself and somewhere useful before you lose it. That makes "getting data out of the sandbox" less an abstraction for me than the whole product. So I have opinions about every door Apple has cut into that wall, and I know roughly when each one appeared.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;iOS has never let two apps share a filesystem. Every method below is a specific, Apple-sanctioned door punched through the sandbox wall, added one at a time.&lt;/li&gt;
&lt;li&gt;The doors moved in one clear direction over eighteen years: from invisible and automatic toward explicit and user-consented.&lt;/li&gt;
&lt;li&gt;Real file-level sharing did not exist until 2014. Everything before iOS 8 moved strings, not files.&lt;/li&gt;
&lt;li&gt;In 2026 I ship exactly two of these mechanisms, and they solve opposite problems.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why was moving data ever hard?
&lt;/h2&gt;

&lt;p&gt;On a desktop, "share data between two programs" is barely a sentence. Any process can read a file in your Documents folder, so the shared filesystem is the integration. iOS threw that assumption out on day one. Each app lives in its own container directory, walled off from every other app's storage, with no ambient shared folder and nothing resembling a common drive. The default is isolation, and it is a security feature, not an oversight.&lt;/p&gt;

&lt;p&gt;So on iOS, "share data" has never been one thing. It has always been a narrower question: which specific, blessed bridge do I use to move this particular payload from my container into somewhere another app or the user can reach? The rest of this piece is those bridges, in the order Apple shipped them, and what each one was actually good for.&lt;/p&gt;

&lt;h2&gt;
  
  
  2009: strings escape first
&lt;/h2&gt;

&lt;p&gt;Three ways out landed close together in iOS 3.0, and all three moved text, not files.&lt;/p&gt;

&lt;p&gt;The first was the clipboard. &lt;code&gt;UIPasteboard&lt;/code&gt; gave every app read and write access to a shared pasteboard, so a user could copy in app A and paste in app B. It was the crudest possible transfer and also the most universal, and for years it was completely silent: an app could read whatever you last copied without you knowing. Hold that thought, because it comes back to bite the whole model in 2020.&lt;/p&gt;

&lt;p&gt;The second was custom URL schemes. An app registered something like &lt;code&gt;myapp://&lt;/code&gt;, and any other app could hand it a small payload by opening that URL. It worked, and it was fragile in every direction. Any app could claim any scheme, so there was no ownership and no trust, and you could only pass what fit in a URL. It was duct tape, but it was the first way one app could deliberately poke another.&lt;/p&gt;

&lt;p&gt;The third was email, through &lt;code&gt;MFMailComposeViewController&lt;/code&gt; in the MessageUI framework. On the surface it is just a mail compose sheet you present inside your app. Underneath it is the strangest and most durable bridge on this list, because it is the only one that does not require the destination app to exist on the device at all. The data leaves the device entirely, lands on a server, and sits in a format that will still open in forty years. I will come back to why that turns out to matter more than it looks.&lt;/p&gt;

&lt;h2&gt;
  
  
  2010 to 2012: files start to move
&lt;/h2&gt;

&lt;p&gt;iOS 3.2 (2010) gave us &lt;code&gt;UIDocumentInteractionController&lt;/code&gt;, better known as the "Open in..." menu. For the first time you could hand a whole file to another app, matched by its type, and let the user pick a target that had registered for it. It was one-way, always user-initiated, and it copied the file into the other app's container rather than sharing it. But it was the first time the unit of transfer was a document instead of a string.&lt;/p&gt;

&lt;p&gt;iOS 5.0 (2011) added something quieter that mattered enormously later: &lt;code&gt;NSFileCoordinator&lt;/code&gt; and &lt;code&gt;NSFilePresenter&lt;/code&gt;, which arrived alongside iCloud document storage. Their whole reason to exist is the idea that two processes might touch the same file, and that they need to take turns so nobody reads a half-written mess. Nobody demoed this at a keynote. It became load-bearing three years later.&lt;/p&gt;

&lt;p&gt;iOS 6.0 (2012) standardized the mess of "Open in..." into &lt;code&gt;UIActivityViewController&lt;/code&gt;, the share sheet. Instead of every app inventing its own export menu, there was now one system menu, and apps registered activities into it. It was still one payload at a time and still something the user had to start, but it turned a scattered set of one-off handoffs into a single, discoverable gesture that every iOS user now knows by heart.&lt;/p&gt;

&lt;h2&gt;
  
  
  2014: the year the sandbox grew real doors
&lt;/h2&gt;

&lt;p&gt;If this history has a hinge, it is iOS 8. Three things shipped together that finally made file-level sharing real.&lt;/p&gt;

&lt;p&gt;App Extensions let your code run inside another app's context: Share extensions, Action extensions, the Today widget, Document Provider extensions, and custom keyboards. Your app was no longer confined to its own window. App Groups gave your own apps and their extensions a genuinely shared container directory, the first sanctioned place two pieces of your own software could read and write the same files. Apple's own guidance came with a warning attached: when you write to that shared container, you must do it "in a coordinated manner to avoid data corruption." That is exactly why the sleepy 2011 file-coordination API suddenly showed up in everyone's code.&lt;/p&gt;

&lt;p&gt;The third piece was iCloud Drive together with &lt;code&gt;UIDocumentPickerViewController&lt;/code&gt; and security-scoped bookmarks. For the first time, an app could, with the user's explicit grant, reach into a folder living in another app's iCloud space and write there, then persist that permission across launches with a bookmark. This is the birth of the modern "append a line into another app's folder" path. I took that exact write path apart layer by layer in &lt;a href="https://dev.to/simple_memo/taking-apart-the-write-path-into-another-apps-icloud-folder-2pdp"&gt;a separate teardown&lt;/a&gt;; here the point is smaller. Before 2014 it was not possible, and after 2014 it was the sanctioned way.&lt;/p&gt;

&lt;h2&gt;
  
  
  2015 to 2017: links grow up, files become visible
&lt;/h2&gt;

&lt;p&gt;iOS 9 (2015) fixed the original sin of custom URL schemes with Universal Links: verified &lt;code&gt;https&lt;/code&gt; links that open your app if it is installed and fall back to your website if it is not. Because the link is a real web address you have proven you own, another app can no longer quietly claim it. The 2009 duct tape finally grew an identity.&lt;/p&gt;

&lt;p&gt;iOS 11 (2017) made the filesystem itself visible. The Files app and &lt;code&gt;UIDocumentBrowserViewController&lt;/code&gt; gave users a place to see and navigate documents across apps and cloud providers, and the document picker got simpler to adopt. The sandbox was still standing, but now there was a shared, user-facing surface, folders, that several apps could point at and a person could actually reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  2020 to 2022: the tide turns toward consent
&lt;/h2&gt;

&lt;p&gt;Then the direction reversed. iOS 14 (2020) made reading the pasteboard raise a banner, so the oldest and most invisible bridge on this list became the most conspicuous one. Apps that had been quietly sniffing the clipboard for years got caught on camera overnight.&lt;/p&gt;

&lt;p&gt;iOS 16 (2022) went further with an explicit "allow paste" prompt and &lt;code&gt;UIPasteControl&lt;/code&gt;, so an app reading the pasteboard without clear user intent now needs permission, and 16.1 added a per-app paste setting. For a decade Apple had spent its energy adding ways to move data. Now it was spending energy adding friction to the ones that moved data without you noticing. The wall was not getting shorter. The gates were getting louder.&lt;/p&gt;

&lt;p&gt;Here is the whole eighteen years on one screen:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Year (iOS)&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;What it moves&lt;/th&gt;
&lt;th&gt;Who starts it&lt;/th&gt;
&lt;th&gt;First-class in 2026?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2009 (3.0)&lt;/td&gt;
&lt;td&gt;UIPasteboard&lt;/td&gt;
&lt;td&gt;A string or blob&lt;/td&gt;
&lt;td&gt;User copies&lt;/td&gt;
&lt;td&gt;Yes, but policed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2009 (3.0)&lt;/td&gt;
&lt;td&gt;Custom URL schemes&lt;/td&gt;
&lt;td&gt;A small URL payload&lt;/td&gt;
&lt;td&gt;Either app&lt;/td&gt;
&lt;td&gt;Superseded by Universal Links&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2009 (3.0)&lt;/td&gt;
&lt;td&gt;MFMailComposeViewController&lt;/td&gt;
&lt;td&gt;A whole message, off device&lt;/td&gt;
&lt;td&gt;User confirms send&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2010 (3.2)&lt;/td&gt;
&lt;td&gt;UIDocumentInteractionController&lt;/td&gt;
&lt;td&gt;One file, by type&lt;/td&gt;
&lt;td&gt;User picks "Open in"&lt;/td&gt;
&lt;td&gt;Mostly folded into the share sheet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2011 (5.0)&lt;/td&gt;
&lt;td&gt;NSFileCoordinator&lt;/td&gt;
&lt;td&gt;Safe access to a shared file&lt;/td&gt;
&lt;td&gt;Your code&lt;/td&gt;
&lt;td&gt;Yes, under the hood&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2012 (6.0)&lt;/td&gt;
&lt;td&gt;UIActivityViewController&lt;/td&gt;
&lt;td&gt;One payload, anywhere&lt;/td&gt;
&lt;td&gt;User opens share sheet&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2014 (8.0)&lt;/td&gt;
&lt;td&gt;App Groups container&lt;/td&gt;
&lt;td&gt;Files, between your own apps&lt;/td&gt;
&lt;td&gt;Your code&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2014 (8.0)&lt;/td&gt;
&lt;td&gt;iCloud Drive + document picker&lt;/td&gt;
&lt;td&gt;Into a user-chosen folder&lt;/td&gt;
&lt;td&gt;User grants, code writes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2015 (9.0)&lt;/td&gt;
&lt;td&gt;Universal Links&lt;/td&gt;
&lt;td&gt;An https route into your app&lt;/td&gt;
&lt;td&gt;Either app&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2017 (11.0)&lt;/td&gt;
&lt;td&gt;Files app + document browser&lt;/td&gt;
&lt;td&gt;Files, user-visible&lt;/td&gt;
&lt;td&gt;User&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2020–22 (14–16)&lt;/td&gt;
&lt;td&gt;Pasteboard consent, UIPasteControl&lt;/td&gt;
&lt;td&gt;Gatekeeping, not transport&lt;/td&gt;
&lt;td&gt;User consents&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  So which of these do I still reach for?
&lt;/h2&gt;

&lt;p&gt;Two of them, and they solve opposite problems.&lt;/p&gt;

&lt;p&gt;The first is email, through &lt;code&gt;MFMailComposeViewController&lt;/code&gt;, the bridge from 2009. I keep coming back to it because it is the only mechanism on the whole list that does not care whether the other app exists. The line leaves the device, lands in an inbox the user already checks every day, and sits there in a format nothing can deprecate. I wrap the send in a small offline queue so a failed attempt retries instead of evaporating, which is a whole design problem of its own that I wrote up as &lt;a href="https://dev.to/simple_memo/an-offline-first-outbox-in-swift-7-steps-no-third-party-libs-4b4d"&gt;an offline-first outbox&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The second is the iCloud Drive document-picker path from 2014, for the case where the destination is a folder the user already owns on their own device. My app uses it to append a dated line to a Markdown vault, which is the one place Obsidian users tend to notice it, and the coordinated write is the only thing standing between "your note got appended" and "your file got corrupted."&lt;/p&gt;

&lt;p&gt;Notice what the two survivors have in common. In both, the destination is the user's own storage or account, an inbox and a folder they control, not another app's private container. The bridges that tried to reach directly into someone else's sandbox are the ones I have quietly stopped using.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I get
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't email a strange way to move data between apps in 2026?&lt;/strong&gt; It looks quaint right up until you notice it is the only mechanism here that works when the target app is not installed, works offline if you queue it, and leaves behind an artifact that will still open decades from now. Quaint and durable are not opposites.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just use the clipboard for everything?&lt;/strong&gt; Because after iOS 14 the clipboard is the most watched channel on the system. It is genuinely good for a user-initiated copy and genuinely wrong for anything your app does on its own. Treat the privacy banner as a design constraint you were handed, not a bug to route around.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where do App Intents and Shortcuts fit?&lt;/strong&gt; They are the newest door, and I left them off the timeline on purpose, because they move actions rather than just data and they deserve their own history. But they are plainly where the direction of travel points: user-authored automation over routes hard-wired by an app developer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where does this history go next?
&lt;/h2&gt;

&lt;p&gt;The eighteen-year arc has one shape. Every generation of cross-app data movement added either user visibility or user consent to the generation before it. The invisible clipboard gave way to a discoverable share sheet, which gave way to an explicit document picker, which gave way to an outright paste prompt. Nothing on this list got easier to do silently. Almost everything got easier to do honestly.&lt;/p&gt;

&lt;p&gt;If that trend holds, the next chapter is not another file API. It is intent, App Intents and Shortcuts and on-device models, working out where a piece of data should go and asking you to confirm, instead of an app quietly hard-wiring the route years in advance.&lt;/p&gt;

&lt;p&gt;Which leaves me with a real question rather than a rhetorical one. What is the oldest cross-app mechanism you still ship in production, a custom URL scheme, an "Open in" handler, a raw pasteboard read, and what finally made you replace it, or decide that you never would? I am trying to sort out which of my own two survivors are principled choices and which are just habit wearing a nice coat, and your holdouts would help me tell the difference.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm the solo developer behind Simple Memo, an iOS app that turns a line you type into an email about a second later, and on the days I curate, appends that same line to a Markdown vault on iCloud. I write here about the unglamorous parts of building it alone. The vault side of that is documented at simplememofast.com/obsidian/.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ios</category>
      <category>apple</category>
      <category>iosdev</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Driving vs. delegating: I timed two ways of pairing with AI</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Fri, 31 Jul 2026 13:33:09 +0000</pubDate>
      <link>https://dev.to/simple_memo/driving-vs-delegating-i-timed-two-ways-of-pairing-with-ai-3pn9</link>
      <guid>https://dev.to/simple_memo/driving-vs-delegating-i-timed-two-ways-of-pairing-with-ai-3pn9</guid>
      <description>&lt;p&gt;There are two ways I pair with an AI coding assistant, and for most of a year I could only have told you which one felt faster, not which one was. One afternoon in June I let an agent rewrite a 300-line module in about a minute, then spent the next two hours cleaning up after it. The size of that gap, and the fact that I had been ignoring it, is the whole reason I sat down to write this.&lt;/p&gt;

&lt;p&gt;By driving I mean keeping my hands on the code. I write most of it, and the model works at the scale of a line or a function: a completion here, a "what's the signature for this API" there, a second read on something I already wrote. By delegating I mean the opposite posture. I describe a whole task, hand it off, walk away, and come back to review a diff I did not write. Same assistant, same me, same afternoon. Two completely different relationships to the code that comes out the other end.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delegating a whole task to an agent is front-loaded speed and back-loaded cost. The diff lands in seconds, but the work just moves downstream into review, and review is exactly where the model's wrong assumptions hide. Driving is the reverse: slow to type, cheap to trust.&lt;/li&gt;
&lt;li&gt;The axis that actually decides which one is faster is not task size. It is verifiability. I delegate when I can check the result faster than I could have written it, and I drive when checking is as expensive as authoring. The rest of this is me arriving at that one sentence the slow way.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What does "faster" even mean here?
&lt;/h2&gt;

&lt;p&gt;The reason I could not answer the speed question for a year is that I was conflating two different clocks.&lt;/p&gt;

&lt;p&gt;There is the clock that runs while the code appears. By that clock, delegating wins every time and it isn't close. I type three sentences of intent, and forty seconds later a diff touches six files. Driving cannot compete with that, and my nervous system knows it: a big diff scrolling past feels like progress in a way that writing one careful function never does.&lt;/p&gt;

&lt;p&gt;Then there is the clock that runs until the code is actually correct and I trust it. That clock includes reading the diff, catching what the model quietly assumed, discovering the integration bug three days later, and re-explaining the constraint it ignored the first time. On that clock the ranking often flips, and the flip is invisible in the moment because the two clocks report at different times. Delegating bills you now and charges you later. I had been reading only the first invoice.&lt;/p&gt;

&lt;p&gt;So the honest version of "which is faster" is a question about the second clock, and the second clock is dominated by one thing: how long it takes me to verify the output.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two postures, side by side
&lt;/h2&gt;

&lt;p&gt;Here is the comparison after tracking it deliberately across a few dozen tasks this spring, in a solo iOS codebase with no team to absorb the review load:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Driving&lt;/th&gt;
&lt;th&gt;Delegating&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;My role&lt;/td&gt;
&lt;td&gt;Author, model assists&lt;/td&gt;
&lt;td&gt;Reviewer, agent authors&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feels fast?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, dangerously so&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where the time goes&lt;/td&gt;
&lt;td&gt;Typing, up front&lt;/td&gt;
&lt;td&gt;Review and rework, after&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mental model of the code&lt;/td&gt;
&lt;td&gt;I hold it the whole time&lt;/td&gt;
&lt;td&gt;I have to reconstruct it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best at&lt;/td&gt;
&lt;td&gt;Tasks where checking is as hard as writing&lt;/td&gt;
&lt;td&gt;Tasks I can verify at a glance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst failure&lt;/td&gt;
&lt;td&gt;I'm slow on work a model could have done&lt;/td&gt;
&lt;td&gt;A wrong assumption ships past a shallow review&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost visibility&lt;/td&gt;
&lt;td&gt;Obvious and immediate&lt;/td&gt;
&lt;td&gt;Hidden and deferred&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The row I care about most is the last one. Driving fails loudly: I feel every slow minute, so I never over-trust it. Delegating fails quietly, and quiet failures are the expensive kind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where delegating clearly wins
&lt;/h2&gt;

&lt;p&gt;When the task is cheap to verify, delegating is not just faster, it is obviously the right call, and I have stopped feeling clever about driving through work like this.&lt;/p&gt;

&lt;p&gt;A new isolated SwiftUI settings screen is the clean example. The correctness is visible: I run it, I look at it, I tab through the fields. If the agent got the layout wrong I see it in five seconds, and the cost of being wrong is a re-render, not a corrupted file. Scaffolding a parser with a table of example inputs and expected outputs is another, because the examples are the check. So is a mechanical refactor, renaming a concept across forty call sites, where the compiler is a far better reviewer than I am and the agent is faster at the typing than I will ever be.&lt;/p&gt;

&lt;p&gt;The pattern under all three is the same. The output carries its own test. I can look at the result and know it is right faster than I could have produced it, so handing off the production is pure profit. This is most of the surface area of a normal codebase, and pretending otherwise, insisting on writing every line by hand, is just craft nostalgia with a time cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where driving quietly wins
&lt;/h2&gt;

&lt;p&gt;Then there is the other kind of task, where the code is not the expensive part.&lt;/p&gt;

&lt;p&gt;The expensive part is noticing the one wrong assumption, and you only notice it if you were already holding the whole model in your head. My Outbox is the case that taught me this. It is a small offline-first queue that retries failed sends, and I have &lt;a href="https://dev.to/simple_memo/an-offline-first-outbox-in-swift-7-steps-no-third-party-libs-4b4d"&gt;written before about how carefully it has to behave&lt;/a&gt; when the network flaps and the app is backgrounded mid-flush. When I delegated a change to its retry path, the agent produced clean, plausible, well-named code that added jitter to the backoff exactly as I asked. It also, without mentioning it, moved a state write outside the lock that was the only thing keeping two retries from waking on the same tick.&lt;/p&gt;

&lt;p&gt;That is not a bug you catch by reading a diff for style. You catch it by having spent a week inside the concurrency model months ago and feeling the wrongness before you can even articulate it. Reviewing that diff cost me more attention than writing the change would have, because to review it properly I had to rebuild the exact mental model that driving would have kept warm the entire time. The same held for the code that appends a line to another app's iCloud folder, where a &lt;a href="https://dev.to/simple_memo/taking-apart-the-write-path-into-another-apps-icloud-folder-2pdp"&gt;subtle mistake in the coordinated-write path&lt;/a&gt; is silent until someone's note goes missing.&lt;/p&gt;

&lt;p&gt;For that class of work, delegating does not save time. It just relocates the hard part from a place where I am strong, authoring with the model in my head, to a place where I am weak, spotting a buried assumption in someone else's plausible code.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what is the actual rule?
&lt;/h2&gt;

&lt;p&gt;For a long time I thought the axis was task size: small things by hand, big things to the agent. That is exactly backwards, and it cost me to learn it.&lt;/p&gt;

&lt;p&gt;The real axis is verification cost. Delegate when you can check the result faster than you could write it. Drive when checking is as hard as writing. Task size barely matters; a 300-line SwiftUI view can be cheaper to verify than a nine-line change to a lock. What I am really doing when I choose a posture is comparing two numbers I can now feel without measuring: the cost to produce, and the cost to check. When check is much less than produce, I hand it off and don't look back. When check approaches produce, I keep my hands on the wheel, because the model's speed is buying me nothing I can safely accept.&lt;/p&gt;

&lt;p&gt;This is also why the fashionable framing, "just get better at prompting and you can delegate everything," misses the point. The bottleneck was never how well I could specify the task. I can specify the Outbox change down to the last detail, and the review is still the expensive part, because an airtight spec does not make a subtle violation of it cheap to detect. The limit is verification, not instruction. It is the same reason I keep &lt;a href="https://dev.to/simple_memo/the-prompt-is-the-cheap-part-the-context-is-the-product-dh0"&gt;context, not prompts, at the center of how I work with a model&lt;/a&gt;: the leverage is in what the model and I can hold in common, not in the wording of the ask.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap I keep falling into
&lt;/h2&gt;

&lt;p&gt;Knowing the rule does not make me immune to it, and I want to be honest about that rather than end on a tidy law.&lt;/p&gt;

&lt;p&gt;The pull toward delegating is not really about speed. It is that a large diff feels like accomplishment, and driving feels like labor, and on a tired afternoon I will reach for the thing that feels like accomplishment even when the task is one I should be holding myself. I have shipped a quiet bug this way more than once, each time on a day when I let the good feeling of a fast diff stand in for the boring question of whether I could actually check it. The tell, when I catch it in time, is that I am about to approve a diff I could not have written, in a part of the system where being wrong is expensive. That specific combination is my signal to stop, close the diff, and drive.&lt;/p&gt;

&lt;p&gt;So the two-way choice has become a one-question habit. Before I accept a handoff I ask whether I can verify this more cheaply than I could have built it. If yes, the agent keeps the work. If I hesitate, the hesitation is the answer, and I take the wheel back.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I keep getting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't this just "use AI for boilerplate"?&lt;/strong&gt; Close, but boilerplate is a proxy, and a leaky one. Some boilerplate is expensive to verify, such as security-sensitive glue, and some novel code is trivial to verify, like a pure function with obvious outputs. Verification cost is the real variable; "boilerplate" is just the case where it is usually low.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doesn't delegating more make you worse at the hard parts over time?&lt;/strong&gt; It is my honest worry. My hedge is that the hard parts, the ones where checking is as costly as writing, are exactly the ones the rule tells me to keep driving. If the rule holds, I stay sharp precisely where sharpness is load-bearing and offload only where it isn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where does an agent's autonomy fit?&lt;/strong&gt; Same axis, higher stakes. The more autonomously it runs, the more code accrues before I review, and the more verification debt piles up if I can't check it cheaply. Long autonomous runs are wonderful for verifiable work and quietly dangerous for the other kind, for exactly the reasons above.&lt;/p&gt;

&lt;h2&gt;
  
  
  The open question I actually want answered
&lt;/h2&gt;

&lt;p&gt;Here is the thing I am still missing, and it is specific. When you delegate a real task to an agent, what is the single check you run before you trust the diff? Not "I read it carefully," but the concrete move: the test you write first, the invariant you assert, the one file you always open, the question you ask the model about its own output. My whole approach now rests on making verification cheap, and I am fairly sure the people who delegate well have a verification move I haven't stolen yet. I would rather learn yours than defend mine.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Simple Memo is the one-person iOS app I make: type a line and it lands in your email a second or so later, so the thought is caught before it evaporates. I post here every few days about what shipping solo keeps teaching me, this piece included. What I'm building lives at &lt;a href="https://simplememofast.com/" rel="noopener noreferrer"&gt;simplememofast.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>agents</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I killed every meeting as a solo dev. Half of it backfired.</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 28 Jul 2026 13:37:13 +0000</pubDate>
      <link>https://dev.to/simple_memo/i-killed-every-meeting-as-a-solo-dev-half-of-it-backfired-58ei</link>
      <guid>https://dev.to/simple_memo/i-killed-every-meeting-as-a-solo-dev-half-of-it-backfired-58ei</guid>
      <description>&lt;p&gt;The line I was proudest of, for about 14 months, was that I ran a product with zero meetings. No standups. No syncs. Not one calendar invite. I said it the way people announce they have quit caffeine: a little smug, waiting for the flinch of envy.&lt;/p&gt;

&lt;p&gt;Then I put one meeting back. A single recurring call, once a month. And the month after I added it, I closed a decision I had been circling for three weeks. The timing nagged at me enough that I went back through my own notes to work out what the meetings I had so proudly deleted were actually doing. The answer was not flattering.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero meetings as a solo dev was half right: the coordination meetings I deleted really were dead weight, because there was nobody on the other side to coordinate with.&lt;/li&gt;
&lt;li&gt;It was half wrong. Meetings also force decisions, put a witness on your commitments, and drop a hard stop on the workday. Those jobs did not vanish when the invites did. I just stopped paying for them on purpose and started paying interest instead.&lt;/li&gt;
&lt;li&gt;The fix was not reinstating meetings. It was rebuilding those specific jobs deliberately, with one standing call, a timeboxed weekly review, and a habit of deciding in public.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The boast, and where I picked it up
&lt;/h2&gt;

&lt;p&gt;I did not invent the anti-meeting position. I inherited it, fully formed, from people I respect.&lt;/p&gt;

&lt;p&gt;Paul Graham's 2009 essay "Maker's Schedule, Manager's Schedule" taught a generation of builders that a maker and a manager run on different clocks, and that a single midday meeting can blow a crater in an afternoon by splitting it into two pieces too small to build anything in. A year later, Jason Fried and David Heinemeier Hansson gave the movement its bumper sticker in &lt;em&gt;Rework&lt;/em&gt;, in a section literally titled "Meetings Are Toxic," where they pointed out that a one-hour meeting with five people is not one hour, it is five. I read both in my twenties and treated them as settled law.&lt;/p&gt;

&lt;p&gt;So when I went solo, dropping meetings felt less like a choice and more like finally getting to practice what I had preached. And here is the part I still stand behind: for a solo developer, most meetings genuinely are waste. A standup coordinates handoffs between people. A sprint planning session negotiates capacity across a team. A status meeting exists so that eight humans can build one shared picture of where things are. When the team is one person, all of that machinery is spinning against no load. It is the same reason I eventually stopped using an &lt;a href="https://dev.to/simple_memo/the-issue-tracker-was-built-for-a-team-i-dont-have-4pli"&gt;issue tracker built for a team I don't have&lt;/a&gt;: the tool was solving a coordination problem I no longer had.&lt;/p&gt;

&lt;p&gt;That much was right, and I want to be clear about it before I take the rest apart. Deleting coordination meetings cost me nothing, because there was nothing there to coordinate.&lt;/p&gt;

&lt;h2&gt;
  
  
  So what were the meetings really doing?
&lt;/h2&gt;

&lt;p&gt;The mistake was assuming coordination was the &lt;em&gt;only&lt;/em&gt; thing meetings did. It was the most visible thing, so I concluded it was the whole thing. It wasn't.&lt;/p&gt;

&lt;p&gt;When I traced my worst weeks back to a cause, the same few functions kept showing up, and every one of them was a job a meeting used to do off to the side, while everyone thought the point of the meeting was the agenda.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What a meeting also did&lt;/th&gt;
&lt;th&gt;My solo substitute&lt;/th&gt;
&lt;th&gt;What it actually cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Forced a decision by a hard date&lt;/td&gt;
&lt;td&gt;"I'll decide once I've thought it through properly"&lt;/td&gt;
&lt;td&gt;Decisions drifted for weeks; a deadline had been doing work I never credited it for&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Put a witness on my commitments&lt;/td&gt;
&lt;td&gt;Kept the plan in my own head&lt;/td&gt;
&lt;td&gt;I quietly abandoned plans nobody had seen me make&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Surfaced disagreement in real time&lt;/td&gt;
&lt;td&gt;Argued both sides with myself on paper&lt;/td&gt;
&lt;td&gt;I shipped confident mistakes with no one to catch them&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dropped a hard stop on the day&lt;/td&gt;
&lt;td&gt;Worked until I drifted off it&lt;/td&gt;
&lt;td&gt;The task sprawled to fill every hour I gave it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;None of these are coordination. All of them are load-bearing. And unlike the coordination overhead, which I could delete for free, these four were structural. Removing the meeting removed the structure and left the need standing there with nothing to lean on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three weeks I lost to a decision nobody forced
&lt;/h2&gt;

&lt;p&gt;Here is the one that finally got my attention, because it was expensive and entirely my own fault.&lt;/p&gt;

&lt;p&gt;I had a schema change to make. Small, reversible, the sort of thing that in a previous job would have come up in a Tuesday sync, gotten fifteen minutes of mild argument, and been decided by Wednesday because the meeting simply ended and a choice had to exist by then. On my own, with no meeting to end, the decision had no deadline attached to it. So I did the responsible-sounding thing and kept thinking about it.&lt;/p&gt;

&lt;p&gt;I thought about it for three weeks. I wrote two documents weighing the options. I felt productive the whole time, because writing documents feels like work. But the schema did not change for 21 days, and the feature sitting on top of it did not ship, and the actual amount of new information I gathered in week three versus week one was close to zero. I had not been deliberating. I had been marinating.&lt;/p&gt;

&lt;p&gt;A meeting would have killed that decision in a day, not because meetings are smart but because they are &lt;em&gt;over&lt;/em&gt; at a specific time, and a decision that has to exist by the time a meeting ends tends to exist. I had thrown away the deadline along with the small talk. This is the thing the anti-meeting essays are quietest about: for someone like me, the meeting was never the interruption. The meeting was the forcing function, and the calendar was doing my executive function for me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I changed without reopening my calendar
&lt;/h2&gt;

&lt;p&gt;I want to be honest that this is where my zero-meetings boast became literally false, and I think that is fine. The goal was never zero meetings. The goal was zero &lt;em&gt;waste&lt;/em&gt;. I had confused the two.&lt;/p&gt;

&lt;p&gt;I rebuilt those functions on purpose, and only those. The forcing function and the hard stop came back together, as clocks. A standing weekly review, 30 minutes with a timer running, where any decision I have been "thinking about" for more than a week has to resolve or get explicitly killed, plus a fixed end-of-day I hold to so the work stops sprawling into the evening. The witness came back as deciding in public. When I commit to something now, I write it somewhere a reader could see it, because a plan with a witness behaves differently from a plan I am keeping in my head, even when the witness is a handful of strangers who will never follow up.&lt;/p&gt;

&lt;p&gt;And the disagreement came back as the one real meeting, the monthly call, with another solo developer who is under no obligation to be nice to me. I had already written about losing &lt;a href="https://dev.to/simple_memo/five-things-shipping-a-product-alone-took-from-me-3ce0"&gt;the one person who tells you you're wrong&lt;/a&gt; when you ship alone. The meeting is where that person used to live. Rebuilding a thin version of them was worth exactly one calendar invite a month.&lt;/p&gt;

&lt;p&gt;Notice what I did not add back: a standup, a status meeting, anything recurring whose job is coordination. Those stayed dead, because those really were waste. I paid to restore the functions I had lost and refused to pay for the ones I hadn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Isn't this just meetings with extra steps?
&lt;/h2&gt;

&lt;p&gt;This is the fair objection, and I have argued it against myself more than once, so let me put it at full strength.&lt;/p&gt;

&lt;p&gt;You could say I have simply reinvented meetings, badly, and that my "timeboxed review" is a standup with a party of one, my "deciding in public" is a status update, and my monthly call is just a meeting I have decided to feel clever about. There is a version of this where I spent 14 months taking the machine apart only to rebuild most of it and claim I invented something.&lt;/p&gt;

&lt;p&gt;I think that objection is about 60% right, and the honest 40% is this. The reconstructed versions are strictly weaker than the real thing at exactly one job, disagreement, and that weakness is not fixable. When I argue both sides of a decision on paper, I am still the one holding the pen, and I cannot fully surprise myself. The monthly call helps, but it is one hour against a whole month of me agreeing with me. I do not actually know yet whether that call is doing real work or whether it is a comfort blanket I have talked myself into needing. Ask me in a year. Forcing, witnessing, and stopping all rebuild cleanly, because they are mechanical and do not need a second brain. Disagreement is the one that genuinely wants another human, and it is the one I am still faking.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I think I'm wrong next
&lt;/h2&gt;

&lt;p&gt;If the pattern holds, the belief I am currently too proud of is that async always beats sync. I have spent this whole post defending a slower, written, meeting-light way of working, and I still believe it for most things. But I have started to notice a specific class of problem, usually a tangled decision with three or four interacting unknowns, where 20 minutes of live back-and-forth with one person gets me further than a week of careful async ever does. I suspect I am still underpricing synchronous bandwidth for exactly the decisions that are too knotty to write down cleanly, and that in a year I will be back here explaining which half of &lt;em&gt;this&lt;/em&gt; post was wrong.&lt;/p&gt;

&lt;p&gt;That is the actual lesson, if there is one. Not "meetings good" or "meetings bad." Just that when you delete a ritual, you should figure out every job it was silently doing first, because some of those jobs do not have the decency to announce themselves on the way out.&lt;/p&gt;

&lt;h2&gt;
  
  
  A couple of questions I keep getting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Doesn't a weekly self-review just become another thing you skip?&lt;/strong&gt; It did, until I put a hard external date on it by tying it to the same day I do my monthly numbers, which I will not skip because money is involved. Attaching the soft ritual to a hard one was the only version that survived.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just get better discipline and decide faster without any of this?&lt;/strong&gt; Because "get better discipline" is the plan that has failed every single time I have tried it, and building a structure that makes the right thing happen on a schedule is the plan that works. I would rather engineer the forcing function than keep losing to my own good intentions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if you had employees?&lt;/strong&gt; Then most of what I deleted would come straight back, because it would be doing real coordination work again, and this entire post would be irrelevant. That is the point. The right number of meetings is a function of how many people have to stay in sync, and for a team of one that number is small, but it is not zero.&lt;/p&gt;

&lt;p&gt;Here is the question I would actually like answered: when you removed a meeting, a standup, a weekly sync, a recurring call, what job did it turn out to be doing that you only noticed once it was gone? I am collecting these, because I suspect forcing, witnessing, and stopping are not the only three.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I'm a solo developer building an iOS app called Simple Memo. It &lt;a href="https://simplememofast.com/" rel="noopener noreferrer"&gt;turns a thought into a sent email&lt;/a&gt; before I can talk myself out of it. I write here every few days about the parts of working alone that nobody warns you about.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>management</category>
      <category>sideprojects</category>
      <category>remote</category>
      <category>discuss</category>
    </item>
    <item>
      <title>A 15-second interruption cost me 40 minutes of re-entry</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Fri, 24 Jul 2026 13:37:08 +0000</pubDate>
      <link>https://dev.to/simple_memo/a-15-second-interruption-cost-me-40-minutes-of-re-entry-4b5f</link>
      <guid>https://dev.to/simple_memo/a-15-second-interruption-cost-me-40-minutes-of-re-entry-4b5f</guid>
      <description>&lt;p&gt;The auth bug was ten minutes from fixed. I had the whole failure loaded in my head: the token refresh racing the retry, the three files it touched, the one line I was reaching for. Then a Stripe email slid across the top of the screen at 3:12 PM. I read it. Fourteen seconds, maybe. Then I looked back at Xcode and the structure was just gone. Rebuilding it took the better part of forty minutes.&lt;/p&gt;

&lt;p&gt;That ratio bothered me enough to start timing it. A fourteen-second glance had cost me forty minutes, and no tool I owned had a place to record that. My calendar knew about the meeting. My timer knew the hours. Nothing knew the tax.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What thirty days of timing actually showed me&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The interruption is the cheap part. Re-entry is where the time goes, and almost nobody measures it.&lt;/li&gt;
&lt;li&gt;My re-entry cost was bimodal. Trivial switches ran about two minutes; deep ones ran thirty to forty-five. There was almost nothing in between.&lt;/li&gt;
&lt;li&gt;The single biggest predictor of which one I got was stupidly simple: whether I had written down my next move before I left.&lt;/li&gt;
&lt;li&gt;Blocking interruptions barely moved the number. Making re-entry cheap moved it a lot.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The setup, and why a solo dev can't just "avoid distractions"
&lt;/h2&gt;

&lt;p&gt;Here is the part the productivity advice skips. As a solo developer I am the engineer, the support desk, the ops on-call, and the person who notices the payment failed. Most of my interruptions are not distractions I can silence. They are the job arriving out of order. "Turn off notifications" is advice written for someone who has colleagues to absorb the things they ignore. I don't have colleagues.&lt;/p&gt;

&lt;p&gt;So after the forty-minute day I stopped trying to block interruptions and started measuring what they cost. One line per interruption for thirty days: the time, what I was doing, what pulled me away, and when I was genuinely back in. That last field turned out to be the one that mattered. I defined "back in" as my first correct edit after returning, not the moment my eyes hit the screen. Those are very different timestamps.&lt;/p&gt;

&lt;p&gt;I want to be honest about the instrument before I quote it. This is one developer timing himself, and on a bad day the faculty doing the timing is the same one the bad day has already degraded. Treat my numbers as a shape, not a benchmark.&lt;/p&gt;

&lt;p&gt;The population number does exist, and it is worse than most people think. Gloria Mark's team at UC Irvine, shadowing office workers through their real days, found it took an average of &lt;strong&gt;23 minutes and 15 seconds&lt;/strong&gt; to return to a task after an interruption, and that people don't go straight back but pass through about two intervening tasks first. Her 2023 follow-up is bleaker: average attention on a screen has fallen to roughly 47 seconds, with an interruption about every three minutes. The 23-minute figure is real. What it hid, for me, was the shape underneath the average.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why is re-entry the expensive part, not the interruption?
&lt;/h2&gt;

&lt;p&gt;Because the interruption doesn't charge you for its own length. It charges you for the teardown.&lt;/p&gt;

&lt;p&gt;When I'm deep in a bug I'm holding a small structure in working memory: which call races which, what I've already ruled out, the exact edge I'm standing on. That structure isn't saved anywhere. It lives in the most volatile storage I own. A fourteen-second read doesn't cost fourteen seconds; it evicts the structure, and then I have to rebuild it from source — reopen the files, re-derive what I'd ruled out, walk back to the edge. Mark's two-intervening-tasks finding is the same story told from the outside: you rarely drop straight back into the thing. You bounce off two smaller things on the way.&lt;/p&gt;

&lt;p&gt;This is the extension of something I found the hard way a couple of months ago, when I was &lt;a href="https://dev.to/simple_memo/i-stopped-counting-hours-and-started-counting-energy-4kam"&gt;logging energy instead of hours&lt;/a&gt;: the villain in that ledger was never interruption either, it was unresolved ambiguity running a background thread and eating the depth I was saving for the hard task. This is the same monster in a different coat. Interruption isn't expensive. Losing your place is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the timer showed: the same switch, two different bills
&lt;/h2&gt;

&lt;p&gt;Once I sorted thirty days of lines, the split was embarrassingly clean. The cost of a switch had almost nothing to do with the interruption and almost everything to do with the one thing I controlled: whether I had left myself a note.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;No next-action written&lt;/th&gt;
&lt;th&gt;Next-action written&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Shallow switch&lt;/strong&gt; (I knew where I was)&lt;/td&gt;
&lt;td&gt;~2 min&lt;/td&gt;
&lt;td&gt;~2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Deep switch&lt;/strong&gt; (mid-problem, stack loaded)&lt;/td&gt;
&lt;td&gt;~34 min&lt;/td&gt;
&lt;td&gt;~6 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The top row barely reacts. When the work was shallow, re-entry was cheap no matter what, because there was no structure to lose. The bottom row is the whole story. A deep switch with nothing written down cost me a median of about &lt;strong&gt;34 minutes&lt;/strong&gt;. The same depth of switch, when I had spent one second writing down my next move, cost about &lt;strong&gt;6&lt;/strong&gt;. Same interruption, same me, a five-fold difference that came down entirely to whether I had left a breadcrumb.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix that didn't work
&lt;/h2&gt;

&lt;p&gt;For a month before this I did what everyone recommends. Notification batching. Do Not Disturb blocks. "Focus hours" fenced off on the calendar. It moved the number so little I almost didn't bother logging it.&lt;/p&gt;

&lt;p&gt;The reason is that blocking assumes the interruption is optional, and mine mostly weren't. The build broke; I had to look. A customer couldn't log in; I had to answer. The pattern the advice is built for, a stream of pings you could ignore without consequence, is not my pattern. You cannot Do-Not-Disturb your way out of being the entire company. Blocking treats the switch itself as the enemy. For me the switch was usually the correct thing to do. The damage came afterward, in the re-entry, and blocking did nothing about that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one that did: cheapen re-entry, don't prevent the switch
&lt;/h2&gt;

&lt;p&gt;So I gave up on preventing switches and put everything into making re-entry cheap. The whole method is one habit: before I leave a task, even for fourteen seconds, I write one line saying exactly what I was about to do next.&lt;/p&gt;

&lt;p&gt;Not "fix auth." That is a task label, and it is useless coming back — it names the mountain, not the step. The line has to be the specific next physical action: &lt;code&gt;refreshToken guard in AuthClient fires before the refresh resolves; change the guard near line 80, not the retry&lt;/code&gt;. That sentence reloads the structure. When I come back I don't rebuild from source; I read one line and step onto the exact edge I left.&lt;/p&gt;

&lt;p&gt;The catch is that this only works if writing the line is nearly free. If capture takes longer than the thought, I won't do it mid-switch, and I'll quietly promise myself I'll remember. So the breadcrumb rides the same one-second capture path as everything else I catch, because &lt;a href="https://dev.to/simple_memo/a-thought-you-cant-capture-in-a-second-is-already-gone-20n9"&gt;a thought I can't get out of my head in about a second is already gone&lt;/a&gt;. One line into my own email, there in about a second; on the evenings I sit down to curate, the same line is already under that day's heading in my vault. The point isn't where it lands. The point is that the exit costs a second and the re-entry it buys back costs half an hour.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I don't want to oversell
&lt;/h2&gt;

&lt;p&gt;This is n=1, self-reported, and the "back in" timestamp is a judgment I made about my own head. The breadcrumb has a real failure mode too: when the interruption &lt;em&gt;changes&lt;/em&gt; the task (a production incident that rewrites what the next action even is), there's no place to return to, and the note is worthless. And none of this is some new law of attention. It's one crude instrument telling one developer he had been counting the wrong number for years.&lt;/p&gt;

&lt;p&gt;Which is the actual takeaway, if there is one. Every tool I own counts interruptions. Not one of them counts re-entry, and re-entry is the number that was eating my days. Measure the thing that costs you, not the thing that happens to be easy to count.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I keep getting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't a breadcrumb just a TODO?&lt;/strong&gt; No, and the difference is the whole point. A TODO records a task: "fix auth." A breadcrumb records a resume point: the specific race, the specific line, the thing I had already ruled out. A task label makes you rebuild the context; a resume point hands it back. You can feel which one you wrote by whether reading it reloads your head or just reminds you the mountain is still there.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Doesn't stopping to write the line just add another interruption?&lt;/strong&gt; It lives inside the switch you were already taking, and it costs about a second against a re-entry that runs into the tens of minutes. I have never once regretted the second. I have lost the afternoon plenty of times by skipping it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What if I can't tell a deep switch from a shallow one in the moment?&lt;/strong&gt; You don't have to. Write the line on every switch. On shallow ones it costs a second and you throw it away; on deep ones it saves you half an hour. It is cheap enough to buy as insurance on all of them, and you stop having to make the call.&lt;/p&gt;

&lt;p&gt;So here is what I actually want from you: what is your own re-entry time after a deep interruption, and have you ever measured it apart from just counting how often you got interrupted? The count is the number every app puts in front of you. I am increasingly sure it is the wrong one, and I would like to know whether your log says the same.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a solo developer building &lt;a href="https://apps.apple.com/us/app/captio-style-simple-memo/id6758438948" rel="noopener noreferrer"&gt;Simple Memo&lt;/a&gt;, an iOS app that gets one line of text into my own email about a second after I think it — roughly the speed a breadcrumb has to be caught to survive a context switch. I write here every few days about the parts of building alone that caught me off guard.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>solodev</category>
      <category>deepwork</category>
      <category>discuss</category>
    </item>
    <item>
      <title>What I measured cutting my iOS cold start to 280 ms</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 21 Jul 2026 13:35:19 +0000</pubDate>
      <link>https://dev.to/simple_memo/what-i-measured-cutting-my-ios-cold-start-to-280-ms-l20</link>
      <guid>https://dev.to/simple_memo/what-i-measured-cutting-my-ios-cold-start-to-280-ms-l20</guid>
      <description>&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage (my iPhone, tap → first usable frame)&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;How I measured it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Pre-main (dyld)&lt;/td&gt;
&lt;td&gt;210 ms&lt;/td&gt;
&lt;td&gt;88 ms&lt;/td&gt;
&lt;td&gt;&lt;code&gt;DYLD_PRINT_STATISTICS=1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;didFinishLaunching&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;120 ms&lt;/td&gt;
&lt;td&gt;21 ms&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;os_signpost&lt;/code&gt; intervals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Root view build + first CA commit&lt;/td&gt;
&lt;td&gt;100 ms&lt;/td&gt;
&lt;td&gt;71 ms&lt;/td&gt;
&lt;td&gt;Instruments, App Launch template&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;430 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;180 ms&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That table is the better part of a year spent chipping at a single number: the time between my thumb landing on the icon and the first usable frame of an iOS app I build alone. On my own phone it fell from 430 ms to 180 ms. In the field, where it actually counts, the number is different and more stubborn, and getting honest about that gap is most of what this post is about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The short version:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The cost of a cold start on a small app is mostly dyld pre-main, and the biggest lever there is how many dynamic libraries you link — not how clever your own code is.&lt;/li&gt;
&lt;li&gt;The rest of the cuts came from refusing to do work on the launch path that most launches never use: hydrating the whole note store, checking iCloud, warming the audio stack.&lt;/li&gt;
&lt;li&gt;The 280 ms in the title is a field median, and it is the least honest number in the post, because the cold start users actually feel is the tail and the first launch after an update — and that one is still over a second.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why does dyld dominate a small app's cold start?
&lt;/h2&gt;

&lt;p&gt;Before your &lt;code&gt;main()&lt;/code&gt; runs, the dynamic linker has already done a day's work. It maps your dependent dynamic libraries, applies rebase and bind fixups so every pointer lands in the right place, registers your Objective-C classes and runs any &lt;code&gt;+load&lt;/code&gt; methods, then runs your C++ static initializers. All of that is "pre-main," and on a small app it is routinely the largest single slice of the cold start. Your own code has not executed yet.&lt;/p&gt;

&lt;p&gt;The number that surprised me when I first turned on &lt;code&gt;DYLD_PRINT_STATISTICS=1&lt;/code&gt; in the scheme's environment variables was how little of pre-main was &lt;em&gt;mine&lt;/em&gt;. The breakdown it prints (dylib loading, rebase/binding, ObjC setup, initializer time) was dominated by dylib loading. I was paying, on every cold start, for the privilege of having split my app into tidy dynamic frameworks.&lt;/p&gt;

&lt;p&gt;Here is the actual shape of it on a fresh install, the numbers &lt;code&gt;DYLD_PRINT_STATISTICS&lt;/code&gt; printed to the console:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Total pre-main time: 210.44 milliseconds (100.0%)
         dylib loading time: 128.19 ms (60.9%)
        rebase/binding time:  41.02 ms (19.5%)
            ObjC setup time:  22.31 ms (10.6%)
           initializer time:  18.90 ms  (9.0%)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sixty percent of my pre-main was the linker finding and mapping binaries. No amount of cleverness inside &lt;code&gt;didFinishLaunching&lt;/code&gt; can touch that slice; it is spent before my code draws breath. The only knob that moves dylib-loading time is how many separate dynamic binaries dyld has to open, and that is a decision you make in your target settings, not your source.&lt;/p&gt;

&lt;p&gt;Two honest caveats before the numbers, because pre-main is the easiest place to fool yourself. First, measure a &lt;strong&gt;release&lt;/strong&gt; build on a &lt;strong&gt;device&lt;/strong&gt;; a debug build on the simulator will lie to you in both directions. Second, modern dyld caches a launch closure after the first run, so a warm cold start is far cheaper than the one that matters most: the first launch after an install or an app update, when the closure is rebuilt. The scary pre-main number is the post-update one, and it is the one &lt;code&gt;DYLD_PRINT_STATISTICS&lt;/code&gt; on a fresh install shows you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three hypotheses I tested
&lt;/h2&gt;

&lt;p&gt;This kind of optimization (measure, guess, cut, measure again) only works if the guesses are written down before the cut. Here are the three I actually tested, in the order the profiler pointed me at them.&lt;/p&gt;

&lt;h3&gt;
  
  
  H1: pre-main is dominated by dynamic library count
&lt;/h3&gt;

&lt;p&gt;Tested by counting. I'd already pulled every third-party dependency out of this app, which &lt;a href="https://dev.to/simple_memo/i-shipped-an-ios-app-with-zero-third-party-dependencies-2jpd"&gt;I wrote about separately&lt;/a&gt;; that turned out to be the single biggest thing standing between me and a fast pre-main, and I hadn't even measured it at the time. But I still had &lt;em&gt;my own&lt;/em&gt; code carved into a couple of embedded dynamic frameworks for module hygiene. Each one is a separate binary dyld has to find, map, and fix up.&lt;/p&gt;

&lt;p&gt;The cut: I collapsed my own modules back into the main app target and let them link statically. Module boundaries are a compile-time nicety; the runtime does not care how clean your dependency graph looked in Xcode. Pre-main dropped from 210 ms to 88 ms on my device — most of it the disappearance of dylib-loading time. Apple has been saying "keep your dynamic framework count low" since the 2016–2017 startup-time talks, and for a small app the advice is close to a free 100 ms.&lt;/p&gt;

&lt;h3&gt;
  
  
  H2: I'm doing store work on the launch path that the first frame doesn't need
&lt;/h3&gt;

&lt;p&gt;Tested with &lt;code&gt;os_signpost&lt;/code&gt;. I wrapped each phase of &lt;code&gt;didFinishLaunching&lt;/code&gt; in a signed interval and read it back in Instruments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;log&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;OSLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;subsystem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"app.launch"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;category&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pointsOfInterest&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;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;OSSignpostID&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;os_signpost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;begin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hydrate-store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;signpostID&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;store&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loadAll&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;          &lt;span class="c1"&gt;// &amp;lt;- the expensive line&lt;/span&gt;
&lt;span class="nf"&gt;os_signpost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;log&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"hydrate-store"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;signpostID&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;store.loadAll()&lt;/code&gt; was a &lt;code&gt;JSONDecoder&lt;/code&gt; reading the entire note history to build the first screen. On my store it cost ~60 ms, and the latent bug was worse than the number: it was O(n) in note count, paid &lt;em&gt;on every launch&lt;/em&gt;. A user with a few thousand notes was subsidising my laziness on the launch path every single time they opened the app.&lt;/p&gt;

&lt;p&gt;The cut: render the first frame from nothing (an empty capture field, the one thing a capture app can always draw instantly) and hydrate the store on a background queue afterward, updating the view when it arrives. The persistence layer already drained &lt;a href="https://dev.to/simple_memo/an-offline-first-outbox-in-swift-7-steps-no-third-party-libs-4b4d"&gt;its outbox asynchronously&lt;/a&gt;, so I was borrowing a pattern I'd already built. In the same pass I moved the iCloud availability check, &lt;code&gt;url(forUbiquityContainerIdentifier:)&lt;/code&gt;, off the launch path and onto first actual use; that call can block for a surprising while the first time you make it. &lt;code&gt;didFinishLaunching&lt;/code&gt; fell from 120 ms to 21 ms.&lt;/p&gt;

&lt;h3&gt;
  
  
  H3: I'm warming a subsystem for a feature most launches never touch
&lt;/h3&gt;

&lt;p&gt;This is the one I'm least proud of. I had been eagerly building the audio and on-device dictation stack during launch so the mic would feel instant the moment someone tapped it. Signposts put it at ~35 ms on the launch path, and it also poked &lt;code&gt;AVAudioSession&lt;/code&gt; awake for no reason on launches where nobody dictated anything.&lt;/p&gt;

&lt;p&gt;The trade I'd made without noticing: I was taxing every cold start to make one occasional tap feel faster. I moved the whole stack to lazy initialization on first mic tap. Cold start got cheaper for everyone; the mic's first-tap latency rose by an amount I could hide behind the waveform animation that plays while it spins up. Warm the thing the user is about to use, not the thing they might use — and never on the path to first frame.&lt;/p&gt;

&lt;h2&gt;
  
  
  The field is not your dev phone
&lt;/h2&gt;

&lt;p&gt;Here is where the tidy lab numbers meet reality. &lt;code&gt;MXAppLaunchMetric&lt;/code&gt; from MetricKit reports the real distribution from real devices, delivered once a day to an &lt;code&gt;MXMetricManagerSubscriber&lt;/code&gt;, aggregated and privacy-preserving. This is field data, not a hero run on the newest phone in the room. It looked nothing like my 180 ms:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MetricKit &lt;code&gt;timeToFirstDraw&lt;/code&gt; (all users)&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;p50 (median)&lt;/td&gt;
&lt;td&gt;~610 ms&lt;/td&gt;
&lt;td&gt;~280 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p90&lt;/td&gt;
&lt;td&gt;~1.2 s&lt;/td&gt;
&lt;td&gt;~520 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst ~1% (post-update, cold, old devices)&lt;/td&gt;
&lt;td&gt;rarely under 2 s&lt;/td&gt;
&lt;td&gt;still ~1.3 s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;My lab number was the ceiling, nothing like the typical case. The field median is roughly 1.5x my device; the p90 is closer to 3x. The gap is older CPUs and slower flash, thermal throttling, low-storage devices where the filesystem is slow, launches while the system is busy doing something else, and above all the first launch after an update, when dyld has no cached closure to lean on. The 280 ms I put in the title is honest as a median. It is also, on its own, a little misleading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which cold start actually matters to a user?
&lt;/h2&gt;

&lt;p&gt;Not the one I spent the most time on. That is the uncomfortable finding.&lt;/p&gt;

&lt;p&gt;The p50 sits below the threshold where a person consciously registers a delay; shaving my own phone from 430 ms to 180 ms was, for the median user, mostly vanity I happened to enjoy. What a user actually &lt;em&gt;feels&lt;/em&gt; is two things I under-invested in. The first is the tail: the p95 and p99, the bad-device, bad-moment launches that still run over a second, because one slow launch is remembered longer than fifty fast ones. The second is that most launches are not cold at all. A capture tool gets opened twenty times a day, and nineteen of those are warm resumes governed by &lt;code&gt;applicationResumeTime&lt;/code&gt;, not the cold path I obsessed over. I optimised the least frequent launch because it was the most satisfying to measure. The honest allocation would have put more of that year into the tail and into resume time.&lt;/p&gt;

&lt;p&gt;I'm keeping the cold-start work (100 ms of free pre-main is still 100 ms, and the store bug was a real scaling landmine), but I'd be lying if I sold you the median as the win. The median was the easy, feel-good part.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I keep getting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this even matter on modern dyld with launch closures?&lt;/strong&gt; For warm cold starts, less than it used to; the closure does most of the caching for you. It matters exactly on the launch you can't cache: the first one after an app update. If you only ever test warm launches, you are measuring the case your users feel least.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SwiftUI or UIKit — does the choice change the cold start?&lt;/strong&gt; At this scale, less than the dynamic-framework count does. The first-frame cost differed by a handful of milliseconds between a minimal UIKit root and a minimal SwiftUI one in my testing; the 100 ms swing came from dyld, not the UI framework. Optimise the linker before you re-litigate your UI stack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Isn't 100 ms of cold start below the human perception threshold anyway?&lt;/strong&gt; For the median, roughly yes, and that is exactly my counter-take above. The reason to still care is the tail: the same cut that moves a 180 ms launch you can't feel also moves a 1.3 s launch you very much can.&lt;/p&gt;

&lt;h2&gt;
  
  
  The number I still can't see
&lt;/h2&gt;

&lt;p&gt;The measurement I want and don't have is the post-update first-launch time-to-first-draw, in the field, isolated from ordinary cold starts. MetricKit buckets them together, so the one launch I believe my users actually judge me on is hidden inside an average with all the cheap warm-cache launches. Next, I'm going to tag it myself: stash the last-launched bundle version in &lt;code&gt;UserDefaults&lt;/code&gt;, compare it on launch, and when it changed, emit a signpost around first draw so I can pull that specific launch out of the noise. My bet, and it is only a bet until the data lands, is that it is the only cold start that has ever cost me a user.&lt;/p&gt;

&lt;p&gt;If you've shipped an iOS app: what is your first-launch-after-update time-to-first-draw, and did you ever manage to measure it apart from ordinary cold starts? That's the number I can't cleanly see, and I suspect it is the only cold start anyone actually feels.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm a solo developer. I build Simple Memo, an iOS app that turns one typed line into an email about a second later, and I keep it small enough that the cold-start ledger above is a normal Tuesday for me. It's &lt;a href="https://apps.apple.com/us/app/captio-style-simple-memo/id6758438948" rel="noopener noreferrer"&gt;on the App Store&lt;/a&gt; if you're curious what that constraint produces.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>swift</category>
      <category>ios</category>
      <category>performance</category>
    </item>
    <item>
      <title>I deleted every Zettelkasten ID and my notes got better</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Fri, 17 Jul 2026 13:33:32 +0000</pubDate>
      <link>https://dev.to/simple_memo/i-deleted-every-zettelkasten-id-and-my-notes-got-better-fkd</link>
      <guid>https://dev.to/simple_memo/i-deleted-every-zettelkasten-id-and-my-notes-got-better-fkd</guid>
      <description>&lt;p&gt;For three years I hand-maintained a Luhmann-style ID on top of every note I kept: roughly 1,400 of them, little addresses like &lt;code&gt;3a2&lt;/code&gt; and &lt;code&gt;3a2b&lt;/code&gt; clipped to the front of each line. Last spring I deleted all of them in one afternoon. The disaster every Zettelkasten guide promises, notes collapsing into an unnavigable pile the moment you remove the structure, never arrived. If anything, the notes got better at the one job I actually need them for now: being read by a machine.&lt;/p&gt;

&lt;p&gt;I want to argue that the famous ID scheme at the heart of the Zettelkasten method solves two problems I no longer have, and quietly charges me for a third I never noticed. And I want to make that argument in the era where a large share of my note retrieval is not done by me at all, but by a language model reading my notes as context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The folgezettel ID was an answer to a physical constraint (paper cannot be searched) and to a human navigator walking a branch by hand. In a plain-text note folder I have neither problem.&lt;/li&gt;
&lt;li&gt;Full-text search and backlinks already reproduce most of what the ID graph did. Even inside the Zettelkasten community this is largely conceded for digital setups.&lt;/li&gt;
&lt;li&gt;The newer reason to drop the IDs: a model reads dated plain prose far better than a lattice of cryptic addresses, and I feed it my notes constantly now.&lt;/li&gt;
&lt;li&gt;What I lost is real. The "at a glance" shape of a thought's branch does not survive the change, so this is a trade, not a free lunch.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The orthodoxy, stated fairly
&lt;/h2&gt;

&lt;p&gt;Niklas Luhmann, the German sociologist, built a paper Zettelkasten of around 90,000 index cards starting in the early 1950s, and credited it as the engine behind a working life of dozens of books and hundreds of papers. The cards were digitized and put online in 2019, so this is not folklore; you can go and look at the actual slips.&lt;/p&gt;

&lt;p&gt;The part everyone borrows is the numbering. Luhmann used what German speakers call &lt;em&gt;folgezettel&lt;/em&gt;, "follow-up notes." The first card is &lt;code&gt;1&lt;/code&gt;. An unrelated new card is &lt;code&gt;2&lt;/code&gt;. But a card that continues, comments on, or branches from card &lt;code&gt;1&lt;/code&gt; becomes &lt;code&gt;1a&lt;/code&gt;; the next in that strand is &lt;code&gt;1b&lt;/code&gt;; a comment on &lt;code&gt;1a&lt;/code&gt; becomes &lt;code&gt;1a1&lt;/code&gt;. The address is not a filing location, it is a position in a tree of thought. A quietly brilliant second-order effect falls out of this: a topic worked for years grows long addresses, so the length of an ID tells you at a glance how deep that vein has been mined.&lt;/p&gt;

&lt;p&gt;Sönke Ahrens' 2017 book &lt;em&gt;How to Take Smart Notes&lt;/em&gt; carried this method out of German academia and handed it to a generation of knowledge workers, and the modern digital Zettelkasten scene (zettelkasten.de, the Obsidian and Logseq crowds) grew from there. The orthodoxy that travelled with it is stated often and stated confidently: the ID is what makes a Zettelkasten a system instead of a junk drawer. Drop the ID and you have a pile.&lt;/p&gt;

&lt;p&gt;I believed that the whole time. I was wrong about it, and the reason I was wrong is the useful part.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why did the IDs feel productive when they weren't?
&lt;/h2&gt;

&lt;p&gt;Because assigning an address felt like thinking, and it wasn't. It was filing.&lt;/p&gt;

&lt;p&gt;Every time I made a note I had to decide where in the tree it hung, which meant re-reading neighbors, comparing, placing. That work produced a warm sense of having done something rigorous. But almost none of it changed what I later understood; it changed only where a card sat. I had confused the ceremony of organizing with the act of thinking, and the ceremony carried a real cost I was paying at every single capture.&lt;/p&gt;

&lt;p&gt;Here is the tell. In all those years I can count on one hand the times I retrieved a note by walking its folgezettel branch. Every other retrieval, thousands of them, was a full-text search. I had built and maintained an elaborate coordinate system and then navigated by coordinate almost never.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the ID was actually for
&lt;/h2&gt;

&lt;p&gt;Two jobs, and both have quietly expired.&lt;/p&gt;

&lt;p&gt;The first job was giving paper a coordinate system. A physical card has no &lt;code&gt;Ctrl-F&lt;/code&gt;. If you cannot search the contents you must know where a thing lives, so you impose an address and a tree on top of it. Luhmann's move was partly a workaround for the fact that a wooden box of cards is opaque to search. My vault is a folder of Markdown files. It is nothing but searchable. The original problem is simply gone.&lt;/p&gt;

&lt;p&gt;The second job was letting a human walk a line of thought by hand, card to card. That one is subtler and more defensible, and I will come back to it, because it is the part I actually gave something up to lose.&lt;/p&gt;

&lt;p&gt;Then there is the job nobody designed the ID for, the one that decided it for me. I now spend a large part of my week handing chunks of my own notes to a language model as context: debugging with it, drafting with it, asking it to reconcile something I wrote in March against something I concluded in June. A model does not traverse my link graph. It reads text, straight through, in order. To that reader a line like &lt;code&gt;2026-03-14 Outbox retry needs jitter or the whole queue wakes on the same tick&lt;/code&gt; is entirely legible. A line that opens with &lt;code&gt;3a2b -&amp;gt;&lt;/code&gt; is noise it has to spend attention decoding. The ID I was maintaining for a human navigator is, to my most frequent reader, litter.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Folgezettel ID&lt;/th&gt;
&lt;th&gt;Dated plain line&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Optimized for&lt;/td&gt;
&lt;td&gt;A human walking a branch&lt;/td&gt;
&lt;td&gt;Search, and a model reading straight through&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per capture&lt;/td&gt;
&lt;td&gt;Compare neighbors, place in the tree&lt;/td&gt;
&lt;td&gt;Stamp the date, type the thought&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How I retrieve&lt;/td&gt;
&lt;td&gt;Navigate the graph by address&lt;/td&gt;
&lt;td&gt;Full-text search or a backlink&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Machine-readable&lt;/td&gt;
&lt;td&gt;Poorly; the address is opaque&lt;/td&gt;
&lt;td&gt;Natively; it is just dated prose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance on insert&lt;/td&gt;
&lt;td&gt;Re-thread, sometimes re-number&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The evidence, including the part that hurts
&lt;/h2&gt;

&lt;p&gt;The community already half-admits the first half of this. Spend an hour on the Zettelkasten forums and you find the &lt;em&gt;folgezettel debate&lt;/em&gt; running for years: whether the branching ID means anything once you have gone digital. The recurring finding is that in a tool like Obsidian you do not need unique IDs to capture the folgezettel structure at all, because backlinks reproduce it. Open a note and its continuations are simply listed as the things that link back to it. The timestamp method, where you date-stamp the note and let search and links carry the rest, is a completely mainstream alternative and not a heresy I invented on my own.&lt;/p&gt;

&lt;p&gt;So the deletion was low-drama. I stripped the &lt;code&gt;3a2&lt;/code&gt; prefixes with a script, kept the dates, and my retrieval, being search all along, never noticed the IDs were gone.&lt;/p&gt;

&lt;p&gt;Now the part that hurts, because I do not trust a case that only flatters the person making it.&lt;/p&gt;

&lt;p&gt;Folgezettel's second job, the at-a-glance topography, does not come back. When the shape of the branch &lt;em&gt;is&lt;/em&gt; the information, when seeing that one vein has grown a forty-character address tells you instantly that this is where your real work has accumulated, backlinks genuinely do not give you that. A list of backlinks is a set, not a shape. For the two years I was building toward one long argument, that topography might have been worth its tax. I killed it because I am not building one long argument. I am running a software product and catching fast-moving thoughts before &lt;a href="https://dev.to/simple_memo/a-thought-you-cant-capture-in-a-second-is-already-gone-20n9"&gt;they evaporate&lt;/a&gt;. Different job, different tool. But I would rather say plainly that I traded something away than pretend I found a strictly dominant move.&lt;/p&gt;

&lt;h2&gt;
  
  
  So when does a Zettel ID still earn its keep?
&lt;/h2&gt;

&lt;p&gt;Three honest cases. When your corpus has to outlive your tools, whether that means paper or a format you will migrate across decades, an ID that is independent of any one app is a real asset. When the branch shape is the product, as in genuine long-form theory-building, the topography is worth maintaining by hand. And when you have no trustworthy full-text search, the coordinate system stops being ceremony and goes back to being load-bearing.&lt;/p&gt;

&lt;p&gt;None of those is true for me. All of them might be true for you, and if so, keep your IDs; the method earned its fame honestly.&lt;/p&gt;

&lt;p&gt;What I run now is almost the opposite of a system. One folder, one dated line per capture, written in nouns and verbs a stranger could parse, no addresses. Search is the index. Backlinks are the graph. And the second reader I actually optimize for is not future-me squinting at &lt;code&gt;1a1&lt;/code&gt;; it is the model I will paste these lines into next week. In the LLM era, the second brain worth keeping is the one the machine can also read.&lt;/p&gt;

&lt;p&gt;I deleted 1,400 IDs and replaced them with the date.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I keep getting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Isn't this just a worse Zettelkasten?&lt;/strong&gt; It is a different bet. Classic Zettelkasten optimizes for a human building a connected argument over years. I optimize for cheap capture and for two readers, search and a model. If your work looks like Luhmann's, mine is the worse choice. If it looks like shipping and debugging, I think the reverse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do you link notes with no IDs?&lt;/strong&gt; Plain backlinks and the date. If two notes belong together I link their titles directly; the date gives me a stable ordering and a rough "what was I chewing on that week" axis. I also split capture and curation into &lt;a href="https://dev.to/simple_memo/i-gave-my-notes-a-cache-hierarchy-phone-l1-vault-l2-73m"&gt;a fast layer and a slow one&lt;/a&gt;, so the linking happens later, while I am reading, not while I am trying to catch the thought.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does full-text search really hold up over years?&lt;/strong&gt; So far, yes. My notes are a couple thousand plain Markdown files and search is instant. The one place it strains is genuinely ambiguous terms, which a date range or a single backlink resolves. Plain text is also the one format I am confident will still be searchable in twenty years, which is more than I can say for any app's database.&lt;/p&gt;

&lt;p&gt;If you run a real folgezettel Zettelkasten, here is what I want from you: the single retrieval you can do that I no longer can. The specific lookup where walking the ID graph beats my search box. Not the philosophy, the concrete case. That is the thing I might have thrown away without noticing.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build Simple Memo by myself: an iOS app that puts one line of text into my email about a second after I type it, and appends the same line to my Markdown vault on the evenings I sit down to curate. I wrote up &lt;a href="https://simplememofast.com/obsidian/" rel="noopener noreferrer"&gt;how the Markdown side works&lt;/a&gt; if that is the part you came for.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>pkm</category>
      <category>obsidian</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>I stopped counting hours and started counting energy</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Tue, 14 Jul 2026 13:10:41 +0000</pubDate>
      <link>https://dev.to/simple_memo/i-stopped-counting-hours-and-started-counting-energy-4kam</link>
      <guid>https://dev.to/simple_memo/i-stopped-counting-hours-and-started-counting-energy-4kam</guid>
      <description>&lt;p&gt;Day 1. I logged 4 hours and 40 minutes of work and shipped nothing I kept. The time tracker said it was a decent day. I had spent most of those 240 minutes re-reading a crash report I did not have the head to fix, and the twenty good minutes went into a settings toggle nobody asked for. That night I closed the timer app for the last time and wrote a different kind of line in my text file: &lt;code&gt;wake 4 / mid 2 / eve 2 - burned on a crash report I couldn't hold in my head.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Day 2. The schema, because a ledger with no schema becomes a diary and a diary becomes nothing. One appended line per day, four fields: three energy readings on a 1-5 scale at roughly 08:00, 14:00 and 21:00, then one clause naming where the energy actually went. No categories. No tags. No app. It takes about fifteen seconds a day, which matters more than any of the rest of it, because a ledger that costs two minutes is a ledger I will abandon by Thursday.&lt;/p&gt;

&lt;p&gt;Day 3. First honest reading: &lt;code&gt;wake 5 / mid 5 / eve 4&lt;/code&gt;. Five hours of real work on the Outbox retry path. The hours tracker would have recorded this as roughly the same day as Day 1. That is the whole complaint in one comparison, and it is the reason I bothered.&lt;/p&gt;

&lt;p&gt;Day 6. I notice I am gaming the scale. Rating my own energy at 21:00 after a bad day feels like grading my own homework, and I catch myself writing &lt;code&gt;3&lt;/code&gt; when the truthful answer is &lt;code&gt;2&lt;/code&gt;, because a &lt;code&gt;2&lt;/code&gt; looks like an accusation. The fix is dumb and it works: I stopped calling it energy in my own head and started calling it &lt;em&gt;available depth&lt;/em&gt; - how many layers of a problem I can hold at once before one slips. Depth is a fact about my brain at that moment. It does not sound like a verdict on my character, so I stopped lying about it.&lt;/p&gt;

&lt;p&gt;Day 9. First real finding, and it annoyed me. My 5s do not arrive at the same time every day, but they cluster hard in the first ninety minutes after I wake up, and I had been spending that window on email and App Store Connect. I had been feeding my best cognition to a task any tired person could do. Swapping those two blocks was the highest-return change of the month and it cost nothing but the discomfort of leaving the inbox unread until 10:00.&lt;/p&gt;

&lt;p&gt;Day 12. A &lt;code&gt;1&lt;/code&gt; day. Total. Slept badly, a support email got under my skin, and I sat in front of Xcode for six hours producing a commit I reverted the next morning. Under the hours regime this would have registered as a full working day and I would have felt vaguely virtuous about it. The ledger just says: &lt;code&gt;wake 2 / mid 1 / eve 1 - support thread; reverted everything.&lt;/code&gt; No credit for the six hours. That is the point of the instrument. It refuses to pay me for attendance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 14. What is this ledger actually measuring?
&lt;/h2&gt;

&lt;p&gt;Not mood. Not productivity. It measures how much of a problem I can hold in working memory at once, which for a solo developer is the only genuinely scarce input. I have unlimited hours in the sense that nobody is stopping me from sitting at the desk. What I do not have is a second person to hand the hard thing to when my head is thin, and that is exactly the assumption every hours-based system is built on. Time tracking was invented to bill clients and to coordinate teams. Neither of those things is happening here. I am the only worker, the only manager, and the only budget.&lt;/p&gt;

&lt;p&gt;Here is the honest comparison after two weeks of running both:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Hours ledger&lt;/th&gt;
&lt;th&gt;Energy ledger&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;What it counts&lt;/td&gt;
&lt;td&gt;Minutes at the desk&lt;/td&gt;
&lt;td&gt;Depth available, 3x a day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How I cheat it&lt;/td&gt;
&lt;td&gt;Sit still, feel virtuous&lt;/td&gt;
&lt;td&gt;Round a 2 up to a 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What it predicts&lt;/td&gt;
&lt;td&gt;Almost nothing&lt;/td&gt;
&lt;td&gt;Whether tomorrow's hard task lands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost to keep&lt;/td&gt;
&lt;td&gt;An app and a habit&lt;/td&gt;
&lt;td&gt;One appended line, ~15 seconds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst failure&lt;/td&gt;
&lt;td&gt;Rewards a bad six hours&lt;/td&gt;
&lt;td&gt;Says nothing about &lt;em&gt;why&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That last row is not a rhetorical concession. It is where the method broke.&lt;/p&gt;

&lt;p&gt;Day 18. The ledger told me I had four low days in a row and offered zero explanation, because I had written the &lt;em&gt;where it went&lt;/em&gt; clause lazily: "app stuff," "app stuff," "admin," "app stuff." Four days of data, no signal. A drain field only works if it names something specific enough to be avoided. So I rewrote the rule: the clause must contain a noun another person could recognise. &lt;code&gt;Xcode archive failing on a provisioning profile I did not change&lt;/code&gt; is a drain. &lt;code&gt;App stuff&lt;/code&gt; is a feeling.&lt;/p&gt;

&lt;p&gt;Day 20. Re-reading the fixed lines, the pattern is embarrassing and clear. Interruption is not what costs me. &lt;em&gt;Unresolved ambiguity&lt;/em&gt; is. A support email I know how to answer takes eight minutes and costs nothing. A support email I do &lt;em&gt;not&lt;/em&gt; know how to answer costs the rest of the morning even if I never open it again, because some part of my head keeps a thread running on it. Ambiguity does not wait its turn. It runs in the background and eats the depth I was saving for the hard task.&lt;/p&gt;

&lt;p&gt;Day 22. So I started closing loops in the cheapest way available, which usually means writing one line and getting it out of my head immediately rather than "handling it properly later." The line goes to my own email in about a second, and at the end of the day it also lands under today's heading in my daily note, so I have stopped carrying it around. None of this is a new insight; it is the reason I built my capture app the way I did, and I have argued before that &lt;a href="https://dev.to/simple_memo/a-thought-you-cant-capture-in-a-second-is-already-gone-20n9"&gt;a thought you cannot capture in about a second is already gone&lt;/a&gt;. What surprised me is that the ledger &lt;em&gt;measured&lt;/em&gt; it. The days after I closed the loops read &lt;code&gt;mid 4&lt;/code&gt;, &lt;code&gt;mid 4&lt;/code&gt;, &lt;code&gt;mid 5&lt;/code&gt;. The days I told myself I would deal with it later read &lt;code&gt;mid 2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Day 25. The counter-take, because I do not trust a system that only ever confirms me. Energy is a lagging, self-reported, one-observer metric. It has none of the properties you would want in an instrument. I cannot calibrate it against anything, I cannot compare it to yours, and on a bad enough day my ability to rate my own state is exactly the faculty that is impaired. Hours, for all their stupidity, are at least objective. If I had a client to bill or a co-founder to coordinate with, I would keep the timer and I would not apologise for it. The energy ledger works for me because my failure mode is not laziness. It is grinding: sitting at the desk at a 1 and calling it work. If your failure mode runs the other way, this instrument will lie to you in the direction you want.&lt;/p&gt;

&lt;p&gt;Day 28. The tally, run with &lt;code&gt;grep&lt;/code&gt; and my own eyes, not a dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Days rated 4-5 at wake: 19 of 30. Days I actually &lt;em&gt;used&lt;/em&gt; that window on the hardest available task: 11.&lt;/li&gt;
&lt;li&gt;The gap between those two numbers is the whole remaining opportunity, and it is larger than anything a productivity app was ever going to hand me.&lt;/li&gt;
&lt;li&gt;Named drains appearing three or more times: unresolved support threads, provisioning and signing errors, and one long-running design decision I kept re-opening instead of deciding.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Day 30. What I keep: the three readings, the drain clause, the fifteen seconds. What I throw away: any attempt to average it, chart it, or turn it into a score. The moment this becomes a number I optimise, I will start optimising the number instead of the work, and I will get very good at scoring 5s on days that produce nothing. The ledger is a mirror, not a KPI. It works precisely because nobody, including me, is grading it.&lt;/p&gt;

&lt;p&gt;Day 31 starts tomorrow, and the only change is a fourth field, on trial: what I &lt;em&gt;deferred&lt;/em&gt; that day. My suspicion, based on Day 20, is that the deferral list predicts the next day's low reading better than anything I currently write down. I will know in a month.&lt;/p&gt;

&lt;h2&gt;
  
  
  What if I already know I'm a morning person?
&lt;/h2&gt;

&lt;p&gt;Then you know one of the four things this told me, and it was the least surprising one. The useful findings were behavioural: which specific class of task was eating the good window, and which specific class of open loop was draining the afternoon. Those are not personality facts. They shift month to month, and I could not have guessed either of them from a chronotype.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is fifteen seconds a day really enough data?
&lt;/h2&gt;

&lt;p&gt;It was enough to change two habits, which is more than eleven months of automatic time tracking ever did for me. Precision is not the constraint here. Survival is. A rich schema I abandon in nine days beats nothing, but it loses badly to a crude schema I still keep on day thirty.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where I want to be told I'm wrong
&lt;/h2&gt;

&lt;p&gt;I want to hear from anyone running both instruments at once who has a real opinion about the overlap: not a philosophy, a specific case where the hours told you something the energy reading missed. And if you keep any daily self-log that survived past a month, name the one field in it you would fight to keep. Mine is the drain clause, and I am fairly sure it is the only field doing real work.&lt;/p&gt;




&lt;p&gt;I develop &lt;a href="https://apps.apple.com/us/app/captio-style-simple-memo/id6758438948" rel="noopener noreferrer"&gt;Simple Memo&lt;/a&gt; by myself: an iOS app that takes one line of text and has it in your email roughly a second later. It is also the thing I use to close the open loops this ledger kept catching me carrying around.&lt;/p&gt;

</description>
      <category>indiehackers</category>
      <category>buildinpublic</category>
      <category>startup</category>
      <category>beginners</category>
    </item>
    <item>
      <title>The issue tracker was built for a team I don't have</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Wed, 08 Jul 2026 01:40:28 +0000</pubDate>
      <link>https://dev.to/simple_memo/the-issue-tracker-was-built-for-a-team-i-dont-have-4pli</link>
      <guid>https://dev.to/simple_memo/the-issue-tracker-was-built-for-a-team-i-dont-have-4pli</guid>
      <description>&lt;p&gt;This is going to be a rant. I'll earn it before the end.&lt;/p&gt;

&lt;p&gt;Here is what set me off. Last Tuesday I spent forty minutes grooming a backlog that exactly one person will ever read. That person is me. I moved cards between columns, I set a priority field on each one, I dragged four of them into something my tool insisted on calling a sprint, and at no point in those forty minutes did I produce a line of code, a fixed bug, or a piece of information I did not already have in my head when I sat down.&lt;/p&gt;

&lt;p&gt;I have run my product alone for over a year. For most of that year I kept reaching, by reflex, for the same category of software every team I ever worked on used to coordinate itself: the issue tracker. Jira, Linear, GitHub Issues, a Trello board with a tasteful column layout — the brand kept changing, the reflex did not. I want to talk about why that reflex is wrong for a solo dev, because it took me an embarrassingly long time to name what was bothering me.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I was actually doing for forty minutes
&lt;/h2&gt;

&lt;p&gt;Break down the ceremony of an issue tracker and almost every ritual in it exists to answer a question that a &lt;em&gt;second person&lt;/em&gt; would ask.&lt;/p&gt;

&lt;p&gt;Story points exist so a team can forecast capacity across several people and argue about it in planning. Status columns exist so that when I hand a ticket to you, you can watch it move from "mine" to "yours" without either of us sending a message. A "definition of done" exists so that two people who disagree about what finished means can settle it in writing before it becomes a fight. Assignee fields, sprint boundaries, WIP limits, triage labels: every one of them is a coordination primitive. They are answers to the problem of getting work through more than one pair of hands.&lt;/p&gt;

&lt;p&gt;I have one pair of hands. When I set a story-point value, I am estimating my own capacity to myself, then reading my own estimate back later and feeling vaguely accused by it. When I move a card from "In Progress" to "Review," there is no reviewer. The card is moving from me to me. I had built an entire apparatus for handing work off, and then I stood on both sides of every handoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this is worse than wasted time
&lt;/h2&gt;

&lt;p&gt;If the tracker only cost me forty minutes a week I would shrug and pay it. The real cost is that the tool quietly reshapes the work to fit its own shape.&lt;/p&gt;

&lt;p&gt;A board wants to be tended. An empty column feels like failure; a full "Done" column feels like progress even in a week where I shipped nothing that mattered. I caught myself making tickets for work I had already finished, purely so I could drag them to Done and feel the small hit of it. That is status theater, and I was performing it for an audience of nobody.&lt;/p&gt;

&lt;p&gt;Then there is the guilt. A stale board is a quiet daily accusation. Forty-one open tickets, six of them older than a quarter, all of them technically "mine" and none of them moving: the board turns an ordinary backlog into a monument to my own unreliability. On a team that pressure is sometimes useful, because a colleague can see the same board and the shared visibility creates real accountability. Alone, it is just a machine for manufacturing low-grade shame, and I was paying a monthly subscription to run it.&lt;/p&gt;

&lt;p&gt;The mismatch underneath all of this is simple. An issue tracker is optimized for state that is &lt;em&gt;shared&lt;/em&gt; and &lt;em&gt;asynchronous&lt;/em&gt;: many people, touching the same work, at different times, needing a durable record of who holds what. My work is neither. It is one person, holding the whole thing in one head, in more or less continuous time. I was using async-coordination software to coordinate with no one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I reach for instead
&lt;/h2&gt;

&lt;p&gt;I keep a single plain-text file. Every line starts with a date and a verb. &lt;code&gt;2026-07-06 fix the timezone bug in the CSV export.&lt;/code&gt; When the line is done I do not drag it anywhere; I put an &lt;code&gt;x&lt;/code&gt; in front of it and move on. There are no columns, because there is nobody to hand a column to. There is no status beyond "still here" or "struck through."&lt;/p&gt;

&lt;p&gt;Time-bound obligations go in the calendar, because a calendar is honestly better than any tracker at the one job it has: telling me a thing must happen on a specific day. And the only real &lt;em&gt;queue&lt;/em&gt; I keep, the only place where work actually arrives from other people, is my email inbox. I wrote a whole separate piece about &lt;a href="https://dev.to/simple_memo/my-inbox-has-been-my-only-task-tracker-for-12-months-3h52"&gt;treating the inbox as the single task queue&lt;/a&gt;, so I won't relitigate it here, except to note the principle is the same one running through the text file: the thing that assigns me work is allowed to be the thing that tracks it, and I refuse to keep a second copy by hand.&lt;/p&gt;

&lt;p&gt;The one rule under all of it is that capturing a task has to be cheaper than the thought that produced it. If writing a task down takes eleven seconds of choosing a project and a label and a priority, I will not write half of them down, and the unwritten half is where the real bugs hide. So capture is a dated line and nothing else. It is the same instinct that made me build Simple Memo, my own capture app, around a single action instead of a menu: the instant you force someone to &lt;em&gt;decide&lt;/em&gt; where a thought goes, you have already lost some of the thoughts. Sorting can happen later, on a quiet Sunday, when sorting is cheap. The capture moment has to stay too dumb to ask questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part where I argue against myself
&lt;/h2&gt;

&lt;p&gt;I said I would earn this rant, so here is the half of it I think is wrong.&lt;/p&gt;

&lt;p&gt;There are cases where an issue tracker earns its place even for one person, and they share a tell: a second human shows up somewhere in the loop. The moment my project has outside contributors, a public bug tracker stops being ceremony and becomes the actual shared surface those people need. I cannot ask a stranger on the internet to append a dated line to my private text file. Anything with a compliance or audit requirement needs the durable, timestamped, who-changed-what record that a tracker gives you for free and a flat file does not. And there is one more that has nothing to do with other people at all: scale over &lt;em&gt;time&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That last one is the trade-off I am genuinely unsure about. My text file is fast to write and miserable to query. Ask me what I was working on in March and I am reduced to grepping the file and reading around the matches. A real tracker would let me filter by label and date in a second. For a project I intend to run for a decade, I may be trading away a queryable history that future-me will badly want, in exchange for a capture speed that present-me enjoys today. I have chosen present-me every time so far. I am not at all certain that is the right person to keep choosing, and I notice I grow less certain the older the file gets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you tell me I'm wrong
&lt;/h2&gt;

&lt;p&gt;Some of you run a personal tracker and love it, and some of you are on teams where the board is the only reason the work ships at all. I am not coming for your board. I am coming for the reflex: the assumption that because real software teams track work in Jira, a party of one should too.&lt;/p&gt;

&lt;p&gt;So here is the question I actually want answered, because I think it exposes the whole thing. What is the one field in your tracker that you fill in on every single ticket and have never once read back? Story points is my bet. Tell me I'm wrong, and tell me which field genuinely earns its keep when nobody but you will ever look at it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build &lt;a href="https://apps.apple.com/us/app/captio-style-simple-memo/id6758438948" rel="noopener noreferrer"&gt;Simple Memo&lt;/a&gt; alone — an iOS app that turns a line of text into a sent email in about a second, then gets out of the way. I post here a couple of times a week, usually when one of my strong opinions survives a fight with my own logs.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>agile</category>
      <category>devjournal</category>
      <category>career</category>
    </item>
    <item>
      <title>Taking apart the write path into another app's iCloud folder</title>
      <dc:creator>Simple Memo</dc:creator>
      <pubDate>Fri, 03 Jul 2026 13:23:09 +0000</pubDate>
      <link>https://dev.to/simple_memo/taking-apart-the-write-path-into-another-apps-icloud-folder-2pdp</link>
      <guid>https://dev.to/simple_memo/taking-apart-the-write-path-into-another-apps-icloud-folder-2pdp</guid>
      <description>&lt;p&gt;I am going to take apart, layer by layer, roughly 90 lines of Swift that let my app write a single line into a file owned by a different app, inside that app's own iCloud Drive container, with no plugin and no server in between. Four layers, one API most iOS developers never touch on purpose, and one bug that cost me a weekend because I trusted &lt;code&gt;FileHandle&lt;/code&gt; more than I trusted Apple's warning label on it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's in this teardown:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Why an iOS app cannot just guess another app's iCloud Drive path, and what &lt;code&gt;UIDocumentPickerViewController&lt;/code&gt; actually grants you&lt;/li&gt;
&lt;li&gt;Why the permission it grants goes stale, and the bookmark trick that survives a relaunch&lt;/li&gt;
&lt;li&gt;Why a plain append can silently corrupt a file mid-sync, and what &lt;code&gt;NSFileCoordinator&lt;/code&gt; changes about that&lt;/li&gt;
&lt;li&gt;The one case none of the above solves, and how I detect it instead of pretending it can't happen&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The thing I'm taking apart
&lt;/h2&gt;

&lt;p&gt;Since the end of May, a small iOS note-to-email app I build has had an optional feature: send a memo, and a timestamped line also lands in a markdown vault stored in iCloud Drive, formatted the way a daily note expects. No server round trip, no companion plugin installed inside the vault. One app writes into a folder it does not own, on a filesystem two different processes are actively syncing at the same time.&lt;/p&gt;

&lt;p&gt;The app is called Simple Memo, and I wrote about the two-layer capture-then-curate shape of its note system &lt;a href="https://dev.to/simple_memo/i-gave-my-notes-a-cache-hierarchy-phone-l1-vault-l2-73m"&gt;in an earlier post&lt;/a&gt;. This one is about the pipe underneath it, not the shape: the part that actually moves bytes across the sandbox boundary. That premise hides four separate problems, and I built the feature in the wrong order the first time. I wrote the "append a string to a file" part first, decided it was basically done, and only found the other three problems by making them happen on my own phone. Here they are, in the order iOS actually enforces them, not the order I discovered them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why can't I just hardcode the vault path?
&lt;/h2&gt;

&lt;p&gt;The instinct is to construct a path: &lt;code&gt;~/Library/Mobile Documents/iCloud~md~&amp;lt;vault app&amp;gt;/Documents/&amp;lt;vault name&amp;gt;/Daily/2026-07-03.md&lt;/code&gt;, or something close to it, and open a &lt;code&gt;FileHandle&lt;/code&gt; on it. On macOS this sometimes works, because the sandbox is looser and Full Disk Access is a checkbox away. On iOS it does not work, on purpose. Every app's iCloud container is a separate sandbox; one app cannot enumerate or open another app's container by path, no matter how well you know the naming convention, because the naming convention is not a contract — it is an implementation detail Apple can and does change between OS versions.&lt;/p&gt;

&lt;p&gt;The only sanctioned door in is &lt;code&gt;UIDocumentPickerViewController&lt;/code&gt;. The user picks a folder (their vault folder, in my case) through the system file picker, and iOS hands your process a security-scoped &lt;code&gt;URL&lt;/code&gt; — a token, effectively, not just a path string. That URL is only valid while you call &lt;code&gt;startAccessingSecurityScopedResource()&lt;/code&gt; on it, and only for as long as the system feels like honoring it in that session. This is the first real design constraint: the user has to grant access once, explicitly, through UI you do not control. There is no way to skip that dialog and no way to pre-select the folder for them. I tried, because a one-tap flow was the whole point of the app; Apple's sandbox model says no, and the "why" is the same reason your banking app can't read your email app's cache: cross-app storage access is opt-in per user gesture, not per developer intent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;requestVaultAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt; &lt;span class="nv"&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;picker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIDocumentPickerViewController&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forOpeningContentTypes&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;folder&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;picker&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;viewController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;present&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;picker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;animated&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="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;documentPicker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;controller&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;UIDocumentPickerViewController&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                     &lt;span class="n"&gt;didPickDocumentsAt&lt;/span&gt; &lt;span class="nv"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;URL&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;vaultURL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&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="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;vaultURL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startAccessingSecurityScopedResource&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;// grant refused or already revoked — surface this, don't silently no-op&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;vaultURL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stopAccessingSecurityScopedResource&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;persistBookmark&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="n"&gt;vaultURL&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;p&gt;That last line, &lt;code&gt;persistBookmark&lt;/code&gt;, is where layer two starts, and it is where I made my first wrong assumption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does the permission go stale between launches?
&lt;/h2&gt;

&lt;p&gt;A security-scoped &lt;code&gt;URL&lt;/code&gt; does not survive process death. Quit the app, relaunch it, and the &lt;code&gt;URL&lt;/code&gt; you stored in memory (or, if you were careless, in &lt;code&gt;UserDefaults&lt;/code&gt; as a plain path string) is worthless — you'll get a permission error on the very first write, every single time, and the failure mode looks exactly like a bug in your write code instead of what it actually is: a missing re-authorization step.&lt;/p&gt;

&lt;p&gt;The fix is a security-scoped bookmark, which is a small opaque &lt;code&gt;Data&lt;/code&gt; blob you create once and can resolve back into a working &lt;code&gt;URL&lt;/code&gt; on every future launch, without asking the user again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;persistBookmark&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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&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;bookmark&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;url&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bookmarkData&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="nv"&gt;includingResourceValuesForKeys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;relativeTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kt"&gt;UserDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bookmark&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"vaultBookmark"&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;resolveVaultURL&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;URL&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;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UserDefaults&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;standard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"vaultBookmark"&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;var&lt;/span&gt; &lt;span class="nv"&gt;isStale&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;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nv"&gt;resolvingBookmarkData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;data&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="nv"&gt;relativeTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nv"&gt;bookmarkDataIsStale&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;isStale&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;if&lt;/span&gt; &lt;span class="n"&gt;isStale&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// the folder moved, got renamed, or iCloud re-issued its identity — bookmark is dead, re-prompt&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;return&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;isStale&lt;/code&gt; is not a hint you can ignore. I ignored it for about two weeks of internal testing, on the theory that a stale bookmark would still resolve to something close enough. It does not. A stale bookmark that you use anyway will sometimes resolve to a URL that looks correct and fails on the write, and sometimes resolve to nothing your app can read at all, and the difference depends on exactly how the underlying folder moved — which is not something I control, because the user can rename their vault, or iCloud can re-shuffle the container during a sync conflict, entirely outside my app's runtime. Once &lt;code&gt;isStale&lt;/code&gt; comes back &lt;code&gt;true&lt;/code&gt;, the only honest move is to drop the bookmark and ask the user to re-grant access through the picker. I now show one line of UI for that ("Reconnect your vault folder") instead of failing the write silently, which is what the app did for those first two weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does a plain append corrupt the file mid-sync?
&lt;/h2&gt;

&lt;p&gt;This is the layer that cost me the weekend, and it is the one most tutorials about &lt;code&gt;FileHandle&lt;/code&gt; do not mention at all, because most tutorials are not writing into a file that a &lt;em&gt;different process&lt;/em&gt; (the iCloud daemon) might be reading, uploading, or rewriting at the exact same moment.&lt;/p&gt;

&lt;p&gt;A naive append looks reasonable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;handle&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;FileHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forWritingTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fileURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seekToEndOfFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lineData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works close to 100% of the time on a phone sitting idle. It becomes unreliable the moment the file is actively syncing — for example, right after the user edited the same daily note on their Mac and iCloud is mid-upload of a newer version when your write lands. I reproduced this by editing the same daily note on my Mac and firing a memo from my phone within the same few seconds, on a loop, forty-some times. Three of those runs produced a daily note with a line inserted in the middle of another line's bytes: not a merge conflict dev.to readers would recognize from git, just corrupted UTF-8 you'd only notice by opening the file and seeing garbled text.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;FileHandle&lt;/code&gt; has no concept of "someone else might be touching this file right now." &lt;code&gt;NSFileCoordinator&lt;/code&gt; does — it is the actual mechanism iCloud Drive apps are supposed to use for exactly this, and it existed the entire time; I just did not reach for it on the first pass because the "write a string to a file" tutorials online almost never mention that a coordinator exists.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;appendLine&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;line&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="n"&gt;to&lt;/span&gt; &lt;span class="nv"&gt;fileURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;throws&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;coordinator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;NSFileCoordinator&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;coordinationError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;NSError&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;writeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt;

    &lt;span class="n"&gt;coordinator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;coordinate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;writingItemAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fileURL&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forMerging&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;error&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;coordinationError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;safeURL&lt;/span&gt; &lt;span class="k"&gt;in&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;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="kt"&gt;FileManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fileExists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;atPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;safeURL&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="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&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="n"&gt;safeURL&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;handle&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;FileHandle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forWritingTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;safeURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;defer&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;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;seekToEndOfFile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&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="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utf8&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;writeError&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;coordinationError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;coordinationError&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;writeError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="n"&gt;writeError&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;p&gt;Two details in that block are the ones I would flag if I were reviewing someone else's PR. First, &lt;code&gt;.forMerging&lt;/code&gt;, not the default writing intent, which assumes you are replacing the whole file. &lt;code&gt;.forMerging&lt;/code&gt; tells the coordinator (and, by extension, any &lt;code&gt;NSFilePresenter&lt;/code&gt; the other app registered, including that app's own sync layer if it's watching) that this is an additive change, which changes how the coordinator schedules your access relative to a concurrent iCloud upload instead of just serializing "biggest write wins." Second, the coordinator hands you a &lt;code&gt;safeURL&lt;/code&gt; inside the closure — not the URL you passed in. Writing to the original &lt;code&gt;fileURL&lt;/code&gt; instead of the coordinator-provided one defeats the entire point; I found that mistake in my own first draft by re-reading the closure a third time, not by a test failing.&lt;/p&gt;

&lt;p&gt;I re-ran the same forty-loop stress test after switching to the coordinator. Zero corrupted files across three separate runs. That is not a controlled benchmark (it is a home-brew stress test on one Mac and one phone), but it is the difference between the bug reproducing reliably and not reproducing at all, and for a background feature nobody watches while it runs, "not reproducing" is the bar that matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive path vs. the coordinated path
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Plain &lt;code&gt;FileHandle&lt;/code&gt; append&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;NSFileCoordinator&lt;/code&gt; + &lt;code&gt;.forMerging&lt;/code&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Survives a concurrent iCloud upload&lt;/td&gt;
&lt;td&gt;No — corrupted 3/40 runs in my test&lt;/td&gt;
&lt;td&gt;Yes — 0/40 in the same test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works after an app relaunch&lt;/td&gt;
&lt;td&gt;Only with a manual re-grant&lt;/td&gt;
&lt;td&gt;Yes, via bookmark resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detects a placeholder (not-yet-downloaded) file&lt;/td&gt;
&lt;td&gt;No — throws a generic "file not found"&lt;/td&gt;
&lt;td&gt;No by itself — needs an explicit download check (below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extra code vs. a plain append&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;About 15 lines&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Requires the user to grant folder access once&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (same requirement either way)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The one thing this doesn't solve
&lt;/h2&gt;

&lt;p&gt;None of the above helps if the target file is an iCloud placeholder your phone hasn't downloaded yet — which happens the first time you point the app at a vault someone else set up on a different device, or after the OS evicts an old file to save local storage. &lt;code&gt;NSFileCoordinator&lt;/code&gt; will hand you a &lt;code&gt;safeURL&lt;/code&gt; that exists in the directory listing and still fails the moment you try to actually read or write its bytes, because the bytes are not on the device.&lt;/p&gt;

&lt;p&gt;You have to check for this explicitly, before you coordinate the write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;values&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;fileURL&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resourceValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forKeys&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;ubiquitousItemDownloadingStatusKey&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;values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ubiquitousItemDownloadingStatus&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="kt"&gt;FileManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startDownloadingUbiquitousItem&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="n"&gt;fileURL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;// and then either poll resourceValues again, or accept the write will&lt;/span&gt;
    &lt;span class="c1"&gt;// land on next app launch — I chose the second, on purpose&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I chose to let the write silently defer to the next launch rather than block the UI on a download of unknown length over an unknown connection. That is the one deliberate trade-off in this whole pipe: I could hold the user's memo in a queue and retry the download-then-write on a timer, which would feel more "real-time," or I could accept that a first-time cross-device vault sync is rare enough that a one-launch delay is an acceptable cost for not adding a retry queue to a feature that already has three other failure modes to track. I picked the second, and I'm not fully sure it's the right call — it's the kind of decision that looks obviously correct until someone hits it on a slow hotel Wi-Fi and can't tell why their note didn't show up.&lt;/p&gt;

&lt;h2&gt;
  
  
  A few questions I keep getting
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Does this need the Files app permission, or a special entitlement?&lt;/strong&gt;&lt;br&gt;
No special entitlement. &lt;code&gt;UIDocumentPickerViewController&lt;/code&gt; for folder access is a standard framework API; the entitlement you do need is the ordinary iCloud Documents capability if you also want your &lt;em&gt;own&lt;/em&gt; app's container to sync, which is a separate concern from writing into someone else's.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does this work if the other app isn't running?&lt;/strong&gt;&lt;br&gt;
Yes, the write happens against the filesystem, not against the other app's process. The receiving app does not need to be open on any device for the append to land; it will pick up the change the next time it's opened or the next time it polls for changes, same as any other iCloud-synced edit made from another device.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why not just use a plugin inside the other app instead?&lt;/strong&gt;&lt;br&gt;
Because that requires the user to install and configure something inside a second app, which is a much heavier ask than granting a folder once through a system picker — and it ties your feature's reliability to another app's plugin API staying stable, which is a dependency I did not want to own.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing that mattered after taking it apart
&lt;/h2&gt;

&lt;p&gt;Every layer here exists because of the same rule: iOS does not trust one app's judgment about another app's files, and it is right not to. The permission model, the bookmark, the coordinator — none of it is incidental complexity you can shortcut past with a clever enough &lt;code&gt;FileHandle&lt;/code&gt; call. The closest thing to a lesson I'd underline twice is that &lt;code&gt;.forMerging&lt;/code&gt; line: the entire corruption bug disappeared behind one enum case I would have found in the documentation on day one if I had gone looking for "how do other apps write into iCloud files safely" instead of "how do I append to a file in Swift." The second question has a five-line answer that is wrong for this exact situation, and the wrong answer looks completely fine until two devices touch the same file within the same second.&lt;/p&gt;

&lt;p&gt;If you've built a write path into a file iCloud is actively syncing, I'd like to know whether &lt;code&gt;NSFileCoordinator&lt;/code&gt; alone solved it for you, or whether you also had to add your own conflict resolution on top of it — that's the part I'm least confident I've fully covered.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I write at &lt;a class="mentioned-user" href="https://dev.to/simple_memo"&gt;@simple_memo&lt;/a&gt;. Simple Memo is the iOS app behind the pipe this post takes apart. On the days I have background note-forwarding turned on, &lt;a href="https://simplememofast.com/en/obsidian/" rel="noopener noreferrer"&gt;the same write path lands a line in my vault&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>swift</category>
      <category>iosdev</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
