Sync and account
How the app shows sync state, resolves conflicts and handles the account, sign-in and sign-out.
Sync control
The Sync control's icon is one of four static cloud glyphs:
| Glyph | Shown for |
|---|---|
| Up to date | Everything is synced. |
| Syncing | Connecting, uploading, downloading, retrying and routine pending work. |
| Needs attention | Only failures, reviews and an ended session. |
| Offline | No connection. |
- Local storage only shows the plain cloud. It is not part of the four-glyph key.
- Unsaved or failed notebook saves take priority over network status.
- The icon never animates. Keep the menu layout.
- Saving is automatic. There is no manual-save setting.
- “Retry local save” appears only for failed saves.
- “Saved on this device” does not mean the server confirmed the change. It covers notebook changes, not open unsaved ToDo drafts.
- “Sync now” is a manual retry. Saving and navigating never require it.
- “Sync now” stays available during an upload, so newer ink can be sent without interrupting it.
Sync details
Top to bottom:
- The status glyph in a 48 dp sage tile and one Medium headline.
- “Sync now”, with “Retry” beside it when a local save failed.
- The last-sync line.
- “Needs your decision”, only when something needs the user.
- A collapsed “Details” disclosure.
- Healthy states show nothing beyond these.
- The headline has a fixed two-line area plus a fixed line for note names, so nothing below moves.
- Text replaces text. There are no spinners or transitions.
Headline
The first matching condition sets the headline:
| # | Condition | Headline |
|---|---|---|
| 1 | A local save failed | “Couldn't save on this device”, with “Retry” beside “Sync now” |
| 2 | Session ended | “Sign in to sync. Your notes are safe on this device.” |
| 3 | Conflicts or rejections | “1 change needs your decision” / “N changes need your decision” |
| 4 | Sync stopped by a non-temporary error | “Some changes couldn't upload. Your notes are safe on this device.” |
| 5 | Offline | “Offline. Your changes are saved on this device.” Add “They will upload when you're back online.” only if something is queued. |
| 6 | First download | “Downloading your notes for the first time” |
| 7 | Temporary error | “Couldn't reach the server. Trying again. Your changes are safe.” It ranks above uploading because the upload is not progressing. |
| 8 | Uploading | “Uploading 2 notes…” |
| 9 | Ink waiting for a trigger | “2 notes not uploaded yet”, with “Sync now” as the primary action |
| 10 | Synced | The synced headline |
- Before the first read of durable state, the headline is “Checking sync…”.
- “Sync now” is secondary in every state except 9.
- Use plain language: no byte counts, protocol stages or counts of queue entries.
- Count notes: the notebooks with queued page or notebook records, or with ink waiting for a trigger.
- Name up to three notes: “Uploading Kubernetes, Journal 6 Nov and 1 other…”.
- ToDos count as “ToDos”; settings count as “changes”.
- Names that do not fit the two-line headline move to the line below it.
- Never show “Everything on this device is synced” or “Up to date” while anything is waiting, retrying, conflicting, rejected or failed to save locally.
Last-sync line
- “Last synced today at 11:42”, “yesterday at …” or the date, in the device locale and time zone.
- Before the first completed checkpoint it reads “Not synced yet”.
- There is no time-zone line; the time zone is in Details.
Needs your decision
One item per conflict, rejected page or rejected record. Each names the note (a journal by its date, a ToDo by its shortened text) and the page number.
| Item | Actions |
|---|---|
| Conflict | “Review” |
| Rejected page | “Retry”, “Use other version” |
| Rejected page the server never accepted | Reads “the page isn't on the server yet”; “Retry”, “Discard my ink” |
| Rejected record | “Retry” |
- “Use other version” and “Discard my ink” both drop this device's edits to that page. “Discard my ink” is used because the server has no version to use.
- List at most 20 items, then a count of the rest.
- A busy action shows its busy state in place and keeps its width.
Details
- A collapsed, static disclosure with no animation.
- It holds the status label, local save state, changes waiting, conflicts, rejected, the time zone and the four-glyph key.
- Rows are 40 dp label/value rows: “Changes waiting | 3 changes”, “Conflicts | None”.
Closing
- Nothing in the dialog needs confirming, so an outside tap closes it.
- Exception: outside taps are ignored while a choice is being applied.
- The dialog has no read-only or signed-out state. Those are full-screen gates.
Conflicts and rejected changes
- The only conflict is a page whose ink changed on two devices.
- Paper settings, deletions and ToDos are last edit wins. They never open a review.
- Deletion wins: a page or note deleted on another device disappears here without a review.
- Ink written for a deleted page or note afterwards is dropped silently.
Conflict review
- Shows this device's page and the other device's page as fixed-size previews, rendered off the UI thread.
- Both previews load before any choice is enabled. “Close” stays available while they load.
- Missing or corrupt data shows as unavailable, never as an empty page.
- An unavailable preview says so inside its preview area.
- Separate reviews are identified by note name (a journal by its date) and page number.
- The heading × stays available. Outside taps are ignored.
| Choice | Result |
|---|---|
| “This device” | Replaces the server's page. |
| “Other device” | Discards this device's edits. |
| “Both” | Keeps this device's page and inserts the other device's version as a new page right after it. |
- The chosen action's label is replaced by its own busy state.
- The review's resolution runs while the review is open. Stylus hover over its disabled canvas cannot block a choice.
- Applying a choice is bounded: it finishes or returns an error, never waits indefinitely.
Rejected changes
- A rejected change shows its plain-language reason and “Retry”.
- A rejection never implies that a server copy is loading.
- “Use other version” drops this device's edits to the page, like the conflict choice “Other device”.
- “Use other version” shows its own busy state, is bounded, reloads the page if it is open and never shows a page banner.
- If the server never accepted the page, the action is “Discard my ink” instead.
Account
- Account state is in plain language, never entitlement enum names.
- The account dialog groups identity, subscription and account actions.
- Security is a separate, bounded dialog.
- Security shows This device, Signed-in devices and Email.
- Signed-in devices show device names and human dates. Never show policies, ISO timestamps or session IDs.
- “Trust this device” is one-way and then shows a Trusted state.
- Dates use the device locale and time zone.
- Subscription operations keep their existing authorization and destructive confirmations.
- There is no read-only state. Editing access is the server's last answer: active or ended.
- Access changes only on a new server answer, never on a clock, a restart or a network error.
Access gates
Full-screen messages that replace the app:
| Gate | Title | Actions |
|---|---|---|
| Subscription ended | “Your subscription has ended” | “Subscribe”, “Restore purchases”, “Sign out”. Notes stay hidden until access is active again. |
| Session expired | “Session expired, sign in to continue” | “Sign in” (same account only; local data is kept), “Sign out” in red (warns that all local data will be deleted) |
| First download | “Syncing with remote…” | None. Shown after Restore purchase until the first download is applied. |
- An expired session keeps the device bound to its account.
- Settings keeps its “Sign out” row.
Signing in
Signed out, the app shows one multi-step modal and nothing else. No social providers are offered.
Every step, and the “Checking your subscription…” status after Restore purchase, is a centred Dialog (Prompt size) on both devices, never a phone bottom sheet. The steps cannot be dismissed; “Back” and system Back move between them.
| Step | Content |
|---|---|
| Welcome | “Sign Up”, “Free Trial”, “Restore purchase”. |
| Sign Up | Step 1 lists CHF 9 / month and CHF 50 / year as fixed text. Step 2 says subscriptions are coming soon and offers “Free Trial” and “Back”. |
| Free Trial | Explains the one-month trial, then asks for the email and the 6-digit code. Success opens the app immediately. |
| Restore purchase | Signs in with an email code or a passkey. |
- On Free Trial, an address that already has an account says so and offers “Restore purchase” and “Sign Up”.
- After Restore purchase signs in, it restores Google Play purchases and checks access as one step with one busy label.
- It then shows the full-screen “Syncing with remote…” until the first download is applied.
Signing out
- Signing out clears the device.
- If everything is synced, it signs out without a dialog.
- Otherwise it warns that unsynced changes will be deleted: “Cancel” / “Sync first” / “Delete and sign out”.
- “Sync first” appears only with an active session.
- “Sync first” uploads and waits up to 60 seconds for confirmation.
- If nothing unsynced remains, it signs out; otherwise the warning appears again.
Limits and behaviour
- Notebook, page, ToDo and settings changes, including ToDo handwriting, upload as soon as the device is online.
- Page ink waits for a trigger: Sync, leaving a page, switching notebooks, leaving the notebook view or leaving the app.
- After a trigger, page ink uploads once per changed tile.
- Leaving the app keeps uploading in the background for up to 60 seconds, then stops the sync connection until the app returns.
- Unfinished work resumes later.
- Navigation never waits for the network.
- Offline, ink stays on the device and uploads once the device is back online.
- A triggered upload resumes after an interruption or restart. Newer, untriggered ink stays local.
- Sync details read everything from durable state, including after a restart.
- A save retry keeps the failure visible until all accepted failed generations are durable.
- Sync status comes from transport state, the SDK's last completed checkpoint and the durable queues.
- A connection alone is not Up to date. It needs a completed, applied checkpoint and no pending changes or reviews.
- Reconnect progress stays visible through its deliberate disconnect.
- Show the actual failure or offline state when it applies.
- Never invent a timestamp.