Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 50 additions & 4 deletions docs/technical_plugin.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ of Geode testable without a running app.
- [What The Plugin Owns](#what-the-plugin-owns)
- [The First Sync Dialog](#the-first-sync-dialog)
- [The Mass Change Dialog](#the-mass-change-dialog)
- [The Status Bar](#the-status-bar)
- [Toasts](#toasts)
- [Guards](#guards)
- [Writing Files Safely](#writing-files-safely)
Expand Down Expand Up @@ -136,12 +137,57 @@ the dialog opens again, and says so: a second identical looking prompt with no e
a bug, and someone who has already clicked through one is exactly the person who will click through
the next without reading it.

### The Status Bar

The status bar is the only thing Geode has on screen all the time, so it has to answer two questions
without being clicked: is it doing something right now, and when did it last work. A spinner with no
counts reads as hung after the first minute, and an idle cloud cannot tell "synced ten seconds ago"
from "has not synced since Tuesday". Automatic sync is what made both of those required rather than
cosmetic: the click used to be the feedback, and there is no click any more.

An icon, a label beside it, and a tooltip. Every state is one row of a table in `status/status.ts`,
and every move between them is a pure transition in the same module, so the plugin holds the value
and draws it and decides none of it.

| State | Says | The tooltip adds |
| -------------------- | ---------------- | ----------------------------------------------- |
| Nothing synced yet | `Not synced yet` | That clicking syncs |
| Resting | `Synced 2m ago` | The same age, and that clicking syncs |
| A pass with no plan | `Checking...` | That it is looking for changes |
| A pass applying one | `Syncing 12/340` | The same count, spelled out |
| A pass failed | `Sync failed` | The reason, and when the vault was last current |
| Automatic sync off | `Sync paused` | That clicking still syncs once |

The count is the plan, reported before the first action runs and once per action after it, whether
that action worked or not. Reporting it up front matters because the first action of a large pull
can outlast anyone's patience on its own, and counting failures matters because a number that stops
moving on the first bad file reads as exactly the hang it is there to disprove.

`Checking...` is not a placeholder. A first sync spends most of its time reading the manifest and
hashing the vault, before there is any plan to count, and that is the stretch the old spinner was
worst at. Saying "0/0" there would be a number pretending to be progress.

Ages are coarse on purpose, minutes then hours then days: nothing finer would be read, and nothing
coarser answers the question. The bar redraws on the scheduler's tick rather than only when
something happens, since "2m ago" is a claim that goes stale on its own.

The time itself lives in vault scoped localStorage, alongside the pause flag and the device ID and
for the same reason: when this laptop last synced is a fact about this laptop, not about the vault.
Repointing at a bucket this vault has never synced forgets it, because the old time is then about
somewhere else, and a confident wrong answer is worse than no answer. Only a state file that was
actually read can say that, though: a read that failed forgets nothing, since "I could not tell"
is not the same answer as "never synced here", and one transient failure should not erase a history
that is still true. A pass that applied nothing still counts as synced, since it proves the vault
and the bucket agree, which is the whole of what the question asks.

The mobile app has no status bar at all, which is half of why toasts exist.

### Toasts

The status bar is a cloud icon and a tooltip, which is enough to answer "what is it doing" and
nothing like enough to say something you have to act on. Toasts are the other half, and every one
geode raises comes from a single table in `notify/notify.ts`, so the wording, the duration, and the
silences are all pinned by one test rather than scattered across the plugin class.
The status bar says what is happening and when it last did, which is nothing like enough to say
something you have to act on. Toasts are the other half, and every one geode raises comes from a
single table in `notify/notify.ts`, so the wording, the duration, and the silences are all pinned by
one test rather than scattered across the plugin class.

| Occasion | Says | Stays |
| --------------------------------- | -------------------------------------------------------- | ----- |
Expand Down
9 changes: 7 additions & 2 deletions docs/technical_sync.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ laptop should never quietly pause your desktop. That is why the pause flag lives
vault scoped localStorage rather than in `data.json`, the same reasoning the device ID uses (see
[Device](technical_device.md)).

While paused, the status bar shows a dimmed cloud with a line through it. Clicking it still syncs
once, and the **Sync** command still works. Pause stops the timer, never the escape hatch.
While paused, the status bar shows a dimmed cloud with a line through it, reading `Sync paused`.
Clicking it still syncs once, and the **Sync** command still works. Pause stops the timer, never the
escape hatch.

### The Scheduler

Expand Down Expand Up @@ -146,6 +147,10 @@ The caller owns persistence. The previous snapshot is passed in and the new one
than read or written internally, so a pass stays pure over its inputs and tests can drive it with
their own store.

Step 7 reports its progress to whoever asked for the pass, once before the first action and once per
action after it, so a long pass can be counted down rather than spun at (see
[Plugin](technical_plugin.md#the-status-bar)).

A pass that planned nothing and found a manifest already describing exactly that skips step 8.
Writing it anyway is not merely a wasted request: every manifest upload is a compare and swap, so a
device with nothing to say is a device that can lose a race it had no reason to enter and report
Expand Down
Loading