Picking the Right Note App Only Feels Like Solving It

Picking the Right Note App Only Feels Like Solving It

1. Notion templates broke the day our team got swamped

A few months ago, our project notes were fine. We were using Notion, built out maybe seven linked databases (tasks, feedback, meeting logs, launch prep), and even stacked on a few community templates that were… okay-ish. Then a big push hit—two client launches and someone out sick—and everything collapsed.

The issue wasn’t that Notion couldn’t handle it; it was that the views were stale at the worst time. Filtering by “assigned to me” or tagging “launch-phase” didn’t always update. People were still seeing old notes. One guy cloned a page, edited it, and didn’t realize it was unsynced from the database view. Classic forked content chaos.

The bug? When duplicating group-filtered database views across pages, the filters stick visibly—but don’t actually apply if the parent template was cached. You only notice it if you check the underlying queries, which, of course, nobody has time for when launching.

We now treat templates like code: versioned, sandbox-tested, and rolled out slowly. Notion doesn’t tell you when a template view is referencing stale state from a previous session. That’s what caused the confusion. My workaround is to include a hidden checklist inside every template with a last-modified timestamp, just so I visually know which version is active.

2. Obsidian sync misfires make shared notes more dangerous

I love Obsidian for how fast it feels. Local-first really means something when you’ve had Google Docs freeze up mid-call while everyone waits. But syncing isn’t as reliable as I thought, and that’s a problem when more than one person edits a vault—even cautiously.

We had one teammate edit a note’s frontmatter to add new tags, while I rewrote a section lower down. Our vault was synced via Obsidian Sync (the paid add-on), and yeah, it didn’t throw a conflict. It just merged both. Sort of.

Turns out Obsidian logs local file saves, not cursor positions, so simultaneous edits can timestamp out of order. YAML got overwritten. Version history showed mine, not theirs. This isn’t a git setup—no 3-way merges. And worse: there’s no warning message when frontmatter changes collide unless they’re syntactically invalid.

The fix? We switched to tagging in-body instead of YAML, and pushed all shared task notes to a separate folder outside the sync scope. It’s dumb, but reliable. Now, every team edit gets pulled individually via a shared Git repo with a commit message. I automated the commit prompts with a shell script triggered by Obsidian’s file change events.

3. Tana’s magic fields are impressive but break when nested

Okay so I reaaaally wanted Tana to be The One. The inline fields, the expandable trees, the AI-autocomplete—feels like a second brain done right. But I hit a weird wall with magic fields when I built a nested workflow tracker.

I created a structure like: “Client → Project → Tasks → Subtasks” and used queries to pull only unfinished items grouped by client. Seemed solid. But the magic fields started behaving unpredictably. Subtasks inside of collapsed nodes were hidden from queries even if not marked done, unless I’d opened their parent nodes during the session.

Found out the system indexes visible nodes first during runtime refresh. This only became obvious when I exported the whole graph, ran a manual JSON check, and saw orphaned items in the graph metadata that weren’t appearing in queries.

“Node X referenced by Node Y was excluded from the children array—visibility status: collapsed.”

That little log is what made it click. Now we tag everything essential with a breadcrumb trail like @client/XYZ and flatten them before queries. The nested hierarchy is only aesthetic; the workflow relies on flat-linked references. Otherwise, query behavior becomes too inconsistent to trust during real work.

4. Craft Docs has beautiful exports but poor automation hooks

I keep coming back to Craft because it makes gorgeous docs with zero effort. Embeds work without fuss. Shared links feel like Medium. But the minute I try to automate publishing or capture anything via API, it’s a mess.

There’s technically a REST API. You can create, read, and update documents. But there’s no webhook trigger. And if you try to use date-matching to figure out what was edited today, it takes the timestamp from last commit—even if that was just the background autosave.

One time I had a Zap set to auto-publish Craft briefs to the web if they were finalized that morning. Worked great until someone proofread an old one. Bam, republished out of order. No notification. Just went live again.

Undocumented edge case: the API returns the timestamp in UTC even when the UI displays it in local time, which threw off my filtering logic completely. The doc looked like it was edited at 11am—except the webhook picked it up as 3pm and skipped it. Subtle but super annoying when running timed automations.

Workaround? I use Make to scrub the timestamp, convert to local with Cloudflare Workers (don’t ask), then log it to Airtable. It’s janky but at least the logic chain is visible.

5. Coda automations collapse silently when values go null

Coda is incredible when things work. Buttons, controls, synced tables—you can build dashboards faster than in any spreadsheet. But last week a client kept triggering a metric review doc and only getting partial outputs. Everything looked fine—buttons pressed, automation “succeeded”—but the status reports were blank.

I dug in and realized that a single row had a missing value in the “Lead Owner” column… which killed the entire automation silently. No error message. No log warning.

Apparently, if you use a value lookup as a condition—like “if [Lead Owner] matches current user”—and that field is blank, the filter returns null and the whole formula short-circuits. Coda doesn’t treat it as false; it stops even evaluating the rest.

This drove me nuts because it shows as green-checked in the automation history. You have to open the automation, click “View Action Input,” and scroll the JSON blob to see that the variable was [null].

Here’s the “aha” move:

If([Lead Owner].IsNotBlank(), DoSomeAction(), Notify("Missing Lead Owner"))

We now add this ternary check everywhere. Otherwise it’s too easy to blow up logic with one rogue blank cell that was imported from the CRM during a rate limit hiccup.

6. Apple Notes isn’t good but people use it anyway

This one’s quick. Apple Notes isn’t powerful, isn’t structured, and syncing via iCloud is weirdly buggy when switching networks. But people still use it. A lot. And journalists love it because it never crashes mid-sentence.

What caught me off guard was discovering that iOS Notes doesn’t expose rich note metadata to Shortcuts. You can list recent notes. You can get the contents. But not creation date, not links, not even UUIDs per note. That forces you to hardcode titles if you want to find things programmatically… which breaks if someone renames it locally.

I tried to build a shortcut that says “grab most recent note from FOLDER X containing the word ‘meeting’”—worked fine during tests. Broke when I traveled to a different timezone. Somehow, Notes reordered the sort behind the scenes based on modified time + device clock.

Shortcuts had no idea. It just grabbed the first list item. For a second I thought my automations went insane. They didn’t. The data layer was just pretending to be stable.

7. Google Keep is kinda trash but nails one use case perfectly

For quick scratchpad ideas shared between two people, Keep wins. Nothing loads faster. You write, it syncs. Great for q&a notes, call prep, and shared shopping lists. That’s about it.

But a few months back I built a little internal system using Keep + Zapier to track high-frequency user feedback tags. I scraped notes containing the word “friction”, extracted connected text, and sent it to a central Google Sheet nightly.

The gotcha? Keep doesn’t let you programmatically pull the label of a note. So if someone tagged it as “Feedback – UX” manually on the mobile app, I had no way to grab that context via the API. It just lists note body + timestamp.

I ended up resorting to hashtag parsing inside the note body. Told the team to always include #UX or #Roadblock. Primitive, but it worked. Well, until someone typed “#notgood” without a space and broke the regex.

Final simplification: every team member now appends a plain code in the last line like [type:UX] or [type:Request]. Regex split got way cleaner. Can’t believe this is what it takes in 2025, but here we are.