Product decisions

Writing into someone else's calendar

Features that touch a user's own data need a rule about what they are allowed to reach, and that rule has to be visible in the product's language too.

Most product features fail politely. A screen is confusing, a label is vague, someone gives up. This one fails differently: if a calendar integration goes wrong, it deletes a real appointment out of a real person’s week.

FootyMate, the football app I build, puts fixtures into the calendar the user already has. Working out how to do that safely turned out to be less about permissions than about ownership — and it changed the copy as much as the code.

The permission is not the boundary

Calendar access is granted once, in a system dialog, and it is broad. After the user taps allow, the app can read, edit, and delete anything in that calendar. Nothing in the platform distinguishes the event the app created from the user’s hospital appointment.

So “the user gave us permission” is not an answer to “what may this feature touch”. It only answers “what is it technically able to touch”. The second question has to be answered by the product.

One rule, everywhere

FootyMate’s rule is that the app can only touch events it created itself.

Every event the app writes is recorded in its own register. Every later operation — updating a moved kickoff, cleaning up after a cancelled match, removing everything when the user turns the feature off — reads from that register and acts only on what it finds there. An event the app did not create is not merely protected. It is unreachable: there is no path in the code that can arrive at it.

That is a small amount of bookkeeping, and it is what makes the feature defensible. Without it, every cleanup routine is one bad query away from deleting someone’s week.

The same shape covers reminders. The alert schedule is rebuilt from current fixture data rather than accumulated over time, so a moved kickoff moves its reminder, a postponed match loses one, and restarting the phone changes nothing. One alert per match, never a duplicate. The rule is again about what the feature owns: it owns the schedule, so it regenerates the schedule, rather than trying to patch a history of past decisions.

The rule has to reach the copy

Here is the part that is a content problem rather than an engineering one.

A user deciding whether to grant calendar access is not evaluating your register. They are answering one question: what is this app going to do to my calendar? If the product’s answer is a paragraph about syncing and integrations, they have learned nothing and will either refuse or agree without understanding — and both of those are failures.

The honest answer is short, and it is only sayable because the rule exists: the app adds its own events and can change only those.

You cannot write that sentence unless someone built it that way. This is the version of “content problem or product problem” I run into most often: the copy is not vague because the writer was careless. It is vague because the product has no commitment precise enough to describe.

The same test, applied to freshness

FootyMate has a second claim it has to be able to make honestly. Fixture data comes from a provider and can be stale. The app’s data layer is cache-first around one rule — better data is never replaced by worse — and every dataset carries the timestamp of its last successful refresh.

That produces one line of interface copy: when fixtures were last updated, or that saved fixtures are showing. It disappears when the data is fresh. A failed refresh never blanks a season the user already has.

Getting that single line to be true took more design than the fixtures list it sits above.

What to ask about any feature that touches user data

  1. What is this feature allowed to reach, as distinct from what it is able to reach?
  2. How does the code know the difference at runtime, on every path, not just the happy one?
  3. What is the shortest true sentence describing that boundary?
  4. If you cannot write sentence 3, which of 1 and 2 has not been decided?

The last question is the useful one. A boundary you cannot describe in a line is usually a boundary that does not exist yet.

The full write-up of how FootyMate works is on its product page.