Transparency
Timetide handles data in two distinct ways — one that never leaves your device, and one that's entirely opt-in. Pick what you want to understand.
Data flow · Personal
01
Your data never leaves your iPhone.
Everything is written to SwiftData — local storage on your device. There's no server-side database because there's no way for the data to get there. Structurally impossible, not just policy.
02
No account. No profile. Nothing.
Your personal data has no server-side destination — there's nowhere to sign into and no account to create. We have no name, email, or identifier that could connect you to your timetable. Can't sell what we don't have.
03
iCloud sync is yours. Not ours.
Enable iCloud sync and your data moves through Apple's encrypted infrastructure — which only you control. We're not in that chain. We never see it pass through.
04
Personal Recap calculated locally.
Every personal stat is calculated on-device. None of it needs to leave to be computed. It's all local arithmetic.
Data flow · Community (opt-in only)
01
Off by default. You choose.
If you opt in, your anonymised data contributes to the community Year in Review — aggregate trends across all contributing students, like subject distribution across faculties or lesson type breakdowns by school. Your personal data is always private regardless. You can opt out at any time.
02
Only what's necessary. Nothing more.
03
Profile on opt-in. Stats weekly.
Profile data (school, faculty, student type) is sent when you opt in. After that it's only updated if you change one of those values — changing your student type doesn't touch your school or faculty count.
Stats data (subject names and lesson type distribution) is sent weekly — the next time you open the app after 7 days have passed. Each sync replaces the previous entry entirely. If you had 2 subjects last week and now have 3, the community total reflects 3, not 5.
04
Profile fields become counters. Stats become one row.
Profile data (school, faculty, student type) is stored as aggregate counts only — the server tracks how many students belong to each value, not a list of who they are. When you change your student type, the old count decrements and the new one increments. No individual record is kept.
Stats data is stored as one anonymous row per user per year, always reflecting your latest state. Each row is keyed only to a random UUID — we can see what an anonymous user contributed, but there is no way to connect that to a person, a name, or any identity.
05
Data belongs to the year it happened.
Stats are bucketed by the year the events actually occurred — not the year the sync runs. Subject and lesson creation is attributed to the year the item was created.
This means if you create lessons in December 2026 but the app syncs in January 2027, that data still counts towards the 2026 Year in Review — not 2027.
06
A random ID. No identity attached.
Each weekly sync needs to update an existing entry rather than create a duplicate, so the app generates a random UUID the moment you first opt in. This UUID is per user — not per device. It has no connection to your name, Apple ID, or any personal information. It's stored in iCloud Key-Value Store — a separate, always-on iCloud mechanism that works independently of the app's iCloud sync setting — so it carries over automatically whenever you get a new device on the same iCloud account.
If you opt out and opt back in, the same UUID is reused — your data slot stays consistent and no duplicate entry is created. A new UUID is only generated if you sign into a different iCloud account, in which case previously contributed data becomes permanently orphaned and cannot be identified or removed.
07
Anonymous data isn't personal data.
Under GDPR, truly anonymous data falls outside the definition of personal data — there's no identifiable data subject and no way to link contributions back to an individual. That's not a loophole. It's why we designed the system this way: anonymise before it leaves your device, aggregate on arrival, store nothing that could identify you.
Right to object — Opt out anytime in Settings → Community Year in Review. Syncing stops immediately. No forms, no waiting.
Supabase EU · GDPR-compliant · Debug builds are excluded from Year in Review totals · Questions: hello@timetide.eu
The sync, field by field
Every field, accounted for.
The complete shape of a weekly sync — taken from the code that sends it. If it isn't here, it never leaves the phone.
Sync payload schema — mirrors the app's sync code
| Field | Type | Example | Why it exists |
|---|---|---|---|
| Profile sent on opt-in — after that, only when a value changes | |||
university | text | Charles University | Powers the schools chart. Set by you in the app — there is no account to read it from. |
faculty | text | Computer Science | Powers the faculty breakdown. |
studentType | text, optional | university | Lets stats be split by study level. Optional — absent if you never set it. |
universityChanged · facultyChanged · studentTypeChanged | bool ×3 | false | Tell the server whether a profile counter needs to move at all. On a normal weekly sync, all three are false. |
previousUniversity · previousFaculty · previousStudentType | text, optional ×3 | — | Included only when a value just changed, so the server can decrement the old counter as it increments the new one. Otherwise absent. |
| Stats one bucket per calendar year, cumulative — replaced in full on every sync | |||
statsByYear | map year → stats | { "2026": … } | Everything below lives in per-year buckets, so December data synced in January still lands in the right review. |
subjectCount | int | 9 | How many subjects you studied that year. |
subjectNames | text[] | ["Linear Algebra", …] | Powers "what the world studied". Subject names only — never lesson names, rooms, or notes. |
scheduledByType | map type → int | lecture: 86, seminar: 41, … | The lesson-types chart. Counts per type, attributed to the year the lesson was created. |
| Sync metadata | |||
deviceId | uuid | 8f3a6c2e-… | Random, generated at opt-in, kept in iCloud key-value storage — per user, not per device, linked to nothing and no one. Lets each sync replace the last instead of double-counting. |
appVersion / appBuild | text ×2 | 1.4 / 213 | Catches version-specific data bugs before they skew the totals. |
environment | text | release | Debug builds flag themselves and are excluded from every total. |
Planned for a future update: anonymous day-of-week and start-hour histograms, which will power the rhythm and start-time charts in the Year in Review. They'll be documented in this table the moment they ship — until then, that data doesn't leave the phone either.
One complete sync — everything that leaves the phone
{
"deviceId": "8f3a6c2e-91d4-4c7b-b2ae-67f0c14e4b9d", // random — created at opt-in
"university": "Charles University",
"faculty": "Computer Science",
"studentType": "university",
"universityChanged": false, // nothing changed → no profile counters move.
"facultyChanged": false, // "previous…" fields are only included
"studentTypeChanged": false, // when one of these is true
"statsByYear": {
"2026": {
"subjectCount": 9,
"subjectNames": ["Linear Algebra", "Operating Systems", "Statistics", "…"],
"scheduledByType": { "lecture": 86, "seminar": 41, "lab": 28 }
}
},
"appVersion": "1.4",
"appBuild": "213",
"environment": "release" // debug builds are excluded from totals
}
No name. No email. No hardware identifiers. No lesson names, no rooms, no notes, no times of day. A sync is the year's running totals — not a copy of your timetable. And it's write-only by design: the app can call exactly one server function — sync. Nothing reads individual rows back out.
Summary
| Data | When sent | How stored | Linked to you? |
|---|---|---|---|
| School name | On opt-in; if changed | Aggregate count | No |
| Faculty | On opt-in; if changed | Aggregate count | No |
| Student type | On opt-in; if changed | Aggregate count | No |
| Subject names & count | On opt-in, then weekly | Per-user row (upsert) | No — UUID only |
| Lesson type distribution | On opt-in, then weekly | Per-user row (upsert) | No — UUID only |
| Your name | Never | — | — |
| Apple ID / email | Never | — | — |