DailyRoundup can import tasks from a Google Tasks list into an existing synced list. Imported tasks land in the paired Reminders list and the next sync cycle pushes them to Trello.
Before the feature can be used, the app must be registered with the Google Cloud Console.
https://www.googleapis.com/auth/tasks (read/write access — required for optional deletion of imported tasks)email (displays the signed-in account in the import breadcrumb)dev.dcwalker.DailyRoundup123456789-abc.apps.googleusercontent.com).The client ID and redirect URI are configured in GoogleTasksClient.swift (clientId and redirectURI constants) and the matching URL scheme is registered in Info.plist. If you are using a different Google Cloud project, replace these values with your own:
static let clientId = "YOUR_CLIENT_ID.apps.googleusercontent.com"
static let redirectURI = "com.googleusercontent.apps.YOUR_CLIENT_ID:/oauth2callback"
<string>com.googleusercontent.apps.YOUR_CLIENT_ID</string>
If the app is published externally (not limited to test users), Google may require verification of the OAuth consent screen before the tasks scope is available to all users. This review process can take several days to weeks. During development, add test user emails under OAuth consent screen > Test users.
| Google Tasks | Reminders | Notes |
|---|---|---|
title |
Title | Subtasks are prefixed with parent title (e.g., “Parent: Subtask”) |
notes |
Notes | Direct mapping |
links |
Notes (appended) | Link URLs appended to notes (e.g. Gmail source) |
due |
Due date | Imported as all-day reminder (Google dates have no time component) |
status |
Completion | completed → marked complete; needsAction → incomplete |
| — | Priority | Default Reminders value (no Google Tasks equivalent) |
Each background-import mapping can carry its own list of Trello labels. Every task imported through that mapping is tagged with the configured labels: the import engine adds each label name to the reminder title as a [Name] bracket token, and the sync server’s bracket→label promotion creates the label on the Trello board if it does not already exist.
Labels are configured per mapping in the Edit Import Mapping screen. Enter a comma-separated list (for example Work, Inbox); on commit, the value is normalized — trimmed, with a single pair of wrapping brackets stripped, deduplicated case-insensitively, and capped at 10 entries. Names containing stray [, ], or newline characters are dropped because they would corrupt the bracket-token parser on the server. If a configured label is already present in the source title’s text (case-insensitive), it is not duplicated.
The user’s account-level label_bracket_position (prefix / suffix) determines where the bracket tokens appear, and the import engine is given it. It used to append unconditionally on the assumption that only the final Trello card name mattered — but the token placement is what the Reminder shows, so an imported task in a prefix account read unlike every other task in the same list (issue #530).
The engine’s applyLabels reproduces what the server’s _apply_create does to the same title on receipt: extract every bracket token already in it, add the configured labels it does not already carry, strip and whitespace-collapse the base, and re-render the whole set at the configured position — single space between adjacent tokens, single space between the token run and the base title. Matching it means the server has nothing to correct on receipt.
The reconcile pass repairs titles already stored in a non-canonical form, so enabling this reaches further than newly imported tasks: any labelled task whose tokens sit at the opposite end from the account setting, are unspaced, or differ in case from the Trello label is rewritten once and pushed to devices. Tasks with no labels and no tokens are left untouched, as are tasks whose Trello card no longer exists.
One consequence is worth naming: a token the user typed in the Google task is relocated to the configured position along with the rest, rather than left where they put it. That is not a preference — the server relocates it regardless, and since issue #530 that rewrite is pushed back to the device, so leaving it in place here would simply be undone in front of the user.
The two implementations cannot share code across the language boundary, so titleMatchesServerRendering in DailyRoundupTests asserts the same renderings the server’s own tests assert. Change one side and change the other.
Google Tasks supports parent/child relationships. Since EventKit has no public API for Reminders subtasks, child tasks are flattened to top-level tasks with the parent’s title prefixed — e.g., a subtask “Buy milk” under “Groceries” becomes “Groceries: Buy milk”.
Each task import is atomic:
If the OS terminates the app during import, the user is informed on next launch and can safely retry.
The Google Tasks API has a 50,000 requests/day quota. If a rate limit is hit, the import stops and the user is notified. Already-imported tasks are unaffected.
Each background-import mapping in Settings shows the outcome of its last run, stored device-locally in UserDefaults:
| State | Icon | Text shown |
|---|---|---|
| Never run | none | “Never imported” |
| Success | green checkmark | Relative time of the last run |
| Partial failure | orange triangle | Relative time of the last run, then how many of the total tasks failed |
| Interrupted | orange pause | Relative time of the last run, then that the run was stopped before it finished |
| Error | red cross | Relative time of the last run, then the reason the run failed |
Error reasons cover a missing Google sign-in, a target synced list that no longer exists, a Reminders list that cannot be resolved on this device, and errors raised while talking to Google: a revoked or expired sign-in, rate limiting, a network failure, an API error, or an unreadable response. A run that fails without recording a reason shows “Import failed for an unknown reason”. The reason is announced through the status icon’s accessibility label, so it is not repeated as a second announcement for the visible text.
A run the system stops part-way through — background refresh reaching its deadline, for example — is recorded as interrupted rather than as a success, since it did not reach every task. It is not counted as a failure either: the imported tasks are kept, the rest are picked up by the next run, and no error notification is posted. If tasks also failed before the run was stopped, the interrupted row names that count too, since those failures are likely to recur.
An error the engine cannot classify falls back to the system description of that error. Status codes and raw API response bodies stay in the logs (subsystem com.dailyroundup, category GoogleImport) rather than the UI, along with a line per task that failed to import. Reasons wrap across as many lines as they need; the text is capped at 200 characters, which affects only unclassified errors and statuses recorded by app versions before this behavior shipped, since those can hold a raw message.
The interactive import screens (sign-in, list pickers, and the add-mapping flow in Settings) run the same classification, so a given failure reads the same wherever it surfaces. Dismissing the Google sign-in sheet is treated as a cancellation, not an error.