Repository navigation
Deliver Android out-of-app writes to the hub via background token - #238
Merged
Merged
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On Android the replica only syncs while the app is in the foreground (ADR-0007). Two kinds of writes happen outside the app and previously sat in a native queue until the main WebView next ran:
ReminderActionReceiverandReminderSnoozeActivity.QuickAddActivity.So a task completed from a notification stayed open on desktop, and a task captured from the status bar never showed up on other devices — the most bug-looking gap in the current behavior.
Solution
While enqueuing, native also enqueues a network-constrained one-shot WorkManager job that posts the write straight to the hub using the device background token. The hub applies the same rules as the client and records a
SyncChange, so other devices pick it up through the normal pull.The native queue stays the single source of pending work: delivery only sets a
deliveredflag and never removes the entry; JS still drains the queue. Delivered completions and quick adds are no longer handed to JS (the task is already in the hub), while delivered snoozes still are, so the queue key keeps protecting the local temporary alarm until JS writes the snooze into the replica.Idempotency comes from deterministic writes instead of a dedup table: settled-at is the tap time, repeat instance ids are derived by rule, snooze targets are computed from the tap time, and quick add uses the entry id as the task id with
deriveSubtaskId(taskId, index)for subtasks. Replays are discarded by the existing stale/ineligible checks, or recognized by id.Changes
Shared rules (one copy)
resolveReminderAction/snoozeTargetmoved frompackages/apito@taskora/enginedomain (api re-exports; callers unchanged), with tests.@taskora/sharedmodulequick-add.ts:QuickAddDrafttypes,toQuickAddDraft, and the pureplanQuickAddTask(draft, refs).createFromQuickAddDraftnow queries refs and applies the plan; behavior unchanged.Hub
out-of-appmodule withPOST /reminders/actionsandPOST /quick-add, both guarded byBackgroundTokenGuardonly (session JWTs are rejected, and the background token still cannot reach any other write endpoint).TasksService.completeaccepts an optionalsettledAt; newcreateFromQuickAddwrites task + reminder + subtasks in one transaction.TasksService.createand the engine backend accept a caller-suppliedid: same user + same id is idempotent, another user's id is a 409.Android native
ReminderStore.PendingActiongains a persisteddeliveredflag;ReminderAlarmsexposesundeliveredActions/markDeliveredand stops handing delivered completions to JS.ReminderActionDelivery+ReminderActionWorker;ReminderBackground.refreshSoonpulls the plan once after a delivery.idwritten into the draft JSON; newQuickAddDelivery+QuickAddDeliveryWorker(reads the reminders plugin's background credential), plusundeliveredQuickAdds/markQuickAddDeliveredanddrainPendingQuickAddsfiltering delivered entries.JS
quickAddEntryId(input)reads the entry id (a submission attribute, not part of the draft);createFromQuickAddDraft(draft, deps, { id })uses it as the task id and derives subtask ids, and skips writing when the task already exists.Docs
.scratch/android-out-of-app-writes/.Testing
out-of-app.e2e-spec.ts(13 cases): completion settled-at and change log, repeat instance derivation without double derivation on replay, snooze 15m / tomorrow across a non-server account time zone, stale/missing discard, invalid action 400, quick add with all fields and derived subtask ids, replay no-op, compacted id not rebuilt, trashed project falls back to Inbox, empty title, JWT rejected, background token rejected onPOST /tasks.quick-add-plan.test.tsfor the shared plan function; movedreminder-action.test.tsto engine; new tests for entry id parsing, id-based idempotent create in the engine backend and hub REST, and controller id passthrough.pnpm -r run typecheck, eslint andtsc --noEmitpass.Notes / follow-ups