Repository navigation
Releases: simonchai-tw/tasks-todo-sync
Release list
v1.0.0
Tasks–To Do Sync v1.0.0
First stable release. The engine fixes in this version came out of a five-axis adversarial review of v0.9.9 — state machines, fuzzing, concurrency, mutation testing, and static consistency — where every finding had to reproduce against the shipped code before it was accepted, and every accepted fix ships with a regression test so it can never silently revert.
What's Changed
⏰ A timed due date edited on Google now reaches Microsoft
- The deadlock is gone: under Time Bridge, changing only the DATE of a due that carries a reminder never landed on Microsoft — the merge leaves the due write to the Microsoft renderer, but the renderer compared its target against Microsoft's unchanged value and concluded there was nothing to do, on every round, forever. The merge now treats its own planned outcome as authoritative.
- Clearing the date clears Microsoft too, in the same round, with the leftover engine-authored reminder cleaned up by one renderer pass.
🧙 A locked-out dry-run can no longer schedule a deployment
- Completes the v0.9.9 claim: when another sync round holds the script lock,
dryRunReport()returns null and the wizard previously dereferenced that null into the error path after the "preview passed" flag was set — so the trigger was created anyway on top of a preview that never ran. The flag is now set only after every value the check depends on has been read.
🔓 An invalid auth mode no longer wedges the next poll
- The user lock is always released:
pollPersonalMicrosoftAuth_()resolved the auth mode before its try block, so an invalidMS_AUTH_MODEthrew while still holding the lock and every later poll deadlocked on the orphan until the execution timed out.
😀 Managed-block display names no longer corrupt emoji
- Truncation counts code points, not code units: a display name ending near the 200-character limit could split a surrogate pair in half, writing a lone surrogate into the managed block in Microsoft notes where it fails to round-trip through JSON and Graph. An emoji at the boundary now survives whole.
✅ Test Suite & Verification
- 549/549 automated tests passing (31s) — 9 new regression tests pin the four fixes, including mutation re-verification that reverting any fix turns its tests red.
- Static gate (
validate.mjs) anddocs:checkgreen; version 1.0.0 consistent across package, docs badge, reliability.json, and installer payload (payload self-identifies as 1.0.0).
v0.9.8
Tasks–To Do Sync v0.9.8
What's Changed
🛡️ Operator Release Exit for Wedged Task-Create Batches
A batch that reached CLEANUP_PENDING with every entry ACKED — and then lost its Microsoft sources mid-flight — had no exit: the finalize guard correctly refused to commit a mapping whose source vanished, but the batch stayed active and every trigger round re-threw the same fatal error forever (TASK_CREATE_FINALIZE_MISSING). Two new operator actions close that hole, gated on live evidence:
previewTaskCreateBatchRelease()re-reads both sides of every item under the global lock and classifies it. Release is allowed only when every item isSOURCE_LOST_DEST_ALIVE,DEST_LOST_SOURCE_ALIVE, orBOTH_LOST; ALIVE+ALIVE, non-ACKED entries, and any failed read are NOT_SAFE.applyTaskCreateBatchRelease(params)clears the batch only after re-verifying the same evidence live, strips create sentinels from surviving Google twins, and hands every survivor back to the ordinary unmapped-create flow, which reposts it with fresh dedup machinery. Nothing is deleted — the same duplicate-risk confirmation asRELEASE_FOR_REPOSTis required.
📦 Installer: 31% Smaller, Fixed Download Name
- 58.2 MB, down from 84.5 MB: TypeScript declaration files (168 MiB unpacked — 71% of the bundled engine, pulled in transitively by
@google/claspand never loaded at runtime) and .NET debugger/designer assemblies are excluded. Runtime behavior unchanged. - Fixed filename
Tasks-ToDo-Sync-Setup.exe: the website binds one permanent download URL (releases/latest/download/Tasks-ToDo-Sync-Setup.exe) that always resolves to the newest release. - Update-check failures read as plain language instead of raw HTTP errors such as "403 (rate limit exceeded)".
✅ Test Suite & Verification
- 530/530 tests pass, including 8 new tests for the release classification matrix, the confirmation gate, batch clearing, and sentinel stripping.
- Installer: 58.2 MB (61,028,434 bytes), sha256
8cf47deeba843b0f1dcb55944afc4a5c5e5c074310b7dcd5e5f5ebfa98dc971b, built from this tag's source; 3-page NSIS wizard unchanged (rc.9 spec).
Full Changelog: v0.9.7...v0.9.8
v0.9.7
Tasks–To Do Sync v0.9.7
What's Changed
🛡️ Recurrence Storm Fixes (2026-10-11)
Microsoft-native recurring tasks are now owned entirely by Microsoft's recurrence engine; the sync engine never writes into them. The 2026-10-11 incident produced 12 duplicate Google copies in 40 minutes: each engine touch on a recurring task (complete/delete/due write) made Microsoft spawn the next occurrence, which the engine then copied to Google as a "new" task, while its own notes merge kept erasing the safety marker.
- The
[TTS-REC]marker can no longer be erased: the marker line that ties a Google copy to its recurring Microsoft task was treated as user content by the notes merge, so an empty Microsoft body wiped it and disarmed the both-recurrence guard. The merge now strips it before comparing and re-attaches it when composing the Google payload. - Completed and due writes are dropped for native recurring tasks (
MS_RECURRENCE_IMMUTABLEin the execution log): writing into a series re-anchors it. Title and notes stay writable. - The Time Bridge renderer skips native recurring tasks entirely: no due or reminder writes into a series Microsoft already owns.
- A Google-side delete of a native recurring task is held (
MS_NATIVE_RECURRENCE): end the series on the Microsoft side instead — that is the one place removal is safe.
✅ Test Suite & Verification
- 522/522 tests pass, including 5 new storm-fix tests (marker preservation, REC-write immunity, delete hold, time-bridge skip).
- Package verified: nuspec
0.9.7, payload DLL sha256 matches the publish output, 3-page NSIS installer with the flat site-consistent icon.
Full Changelog: v0.8.4...v0.9.7
v0.8.4
Tasks–To Do Sync v0.8.4
Connection-layer UrlFetch failures are now retried in every language. Yesterday a single sync round failed with Google's localized 「無法開啟網址」 (cannot open URL) for the Microsoft Graph endpoint — 29 rounds succeeded around it, so this was a one-off network blip between the Apps Script and Microsoft data centers, not a code defect. The real defect was how the engine reacted: the retry allowlist matched only English message fragments, so a connection failure reported in any other language skipped the in-round retry entirely.
What's Changed
🌐 Transient Semantics Inverted — Language-Independent Retries
- Semantic inversion: a UrlFetchApp exception means the request never produced an HTTP response — DNS failure, connection reset, timeout, bandwidth quota, or a localized cannot-open-URL in any language. All of them are transient by default and retried with the existing exponential backoff (1s → 2s, time-budget aware).
- Only argument-level mistakes are excluded as permanent (
invalid argument/invalid value/invalid url) — a three-entry exclusion list that is immune to Google rewording or locale. - The old English-fragment allowlist is gone — it could never survive Google's translations.
📧 Alert Behaviour Unchanged
- The fatal-error email still fires only after all in-round retries are exhausted, and the 24-hour cooldown still caps it at one per day. A transient blip that a retry absorbs now produces no email at all.
🧪 Date-Bomb Guard in Action
- The v0.8.3 static scan caught two more calendar-literal fixtures the very next day (a pinned "tomorrow" drifted into the danger window) — marked
FIXED-DATE-OKas static nowMs-injected fixtures, proving the guard pays for itself immediately.
✅ Test Suite & Verification
- 470 automated tests passing (100% PASS) — new regressions cover localized zh-TW and English cannot-open-URL failures (retried to exhaustion with correct backoff) alongside the existing permanent-error path.
- Static gate (
validate.mjswith the date-bomb scan) and package dry-run validation pass. The fix is deployed to the maintainer's live project.
v0.8.3
Tasks–To Do Sync v0.8.3
Three boundary bugs found by the external advisory review (meta.ai, 2026-10-01) are fixed — each was verified against the actual code paths before any change was made. Alongside the fixes, the test suite's date-bomb fixtures were defused and a static guard now prevents the same class of failure from recurring.
What's Changed
🔍 Advisory A — Parent-Loss Evidence Is Now Actually Produced
- Root cause:
discoverRelationshipsReadOnly_classified Microsoft parents asPARENT_NOT_FOUNDbut never collected their ids into the result, so the subtask-specific Microsoft parent-loss branch insubtaskObserveDeletionEvidence_could never fire. - Fix: the discovery result now carries
notFoundParentIds, and the observation bridge feeds them intosnap.msParentNotFound— the branch works as designed.
🧯 Advisory B (P0) — Deletion Journal Recovery No Longer Deletes With the Wrong List Id
- Root cause: journal rows deliberately excluded
gListIdfrom persistence, so a recovery round fell back torow.parentMsListId— a Microsoft list id — against the Google API. The guaranteed 404 was misread as not-found and the journal finalized, orphaning the Google checklist child. - Fix:
gListIdis now persisted (state schema allowlist extended and id-validated), and a row without it parks inUNCERTAINinstead of faking a not-found finalize — fail-closed, never fail-open.
⏱️ Advisory C — Cross-List Moves Keep the Canonical Time Record
- Root cause:
putMapping_rebuilt mapping rows withouttd, discardingtd.retryandtd.quarantineon a cross-list move and reviving quarantined pairs. - Fix: the canonical time record survives mapping rebuilds, same as
fp/res/fc/rem.
🧨 Date-Bomb Fixtures Defused — and a Guard So It Never Recurs
- History repeated itself: v0.7.0 shipped test fixtures pinned to the release date (broke once, patched by WO-2), and v0.8.0's rewrite reintroduced the same pattern — the
timeBridgeRun_integration test broke again the moment the real calendar advanced. - Defused: the integration fixture is now anchored on the real clock with a marker 25 hours ahead — always future, never drifts into the expiry-lift window.
- Guarded:
validate.mjsnow fails any test date literal within ±1 day of today unless explicitly markedFIXED-DATE-OK, so the next rewrite cannot quietly reintroduce the bomb.
✅ Test Suite & Verification
- 469 automated tests passing (100% PASS) — four new regressions cover each advisory fix: the discovery id collection, both journal-recovery paths (persisted id delete + legacy-row UNCERTAIN parking), and
putMapping_tdpreservation. - Static gate (
validate.mjs, now including the date-bomb scan) and package dry-run validation pass; npm publish follows the maintainer's 2FA re-login.
v0.8.2
Tasks–To Do Sync v0.8.2
Fixes the recurring +20-minute reminder drift on Microsoft To Do alarms. If a task's reminder time had already passed when a sync round ran, the R-1 guard pushed the alarm to "now + 20 minutes" every round, read its own write back as a hand-set edit, and pushed again — recurring tasks replayed the loop every day. The guard now fires once when a time record is created; after that the alarm is written exactly as authored, hand-set Microsoft times and recurrence engines are left alone, and all Time Bridge features (time sync, calendar projection, marker intake) are unchanged.
What's Changed
⏰ R-1 Reminder Guard Now Fires Once, Not Every Round
- Root cause: the renderer recomputed
max(due instant, now + 20 minutes)on every sync round, so its own write never converged; the adopt path then read that drift back as a user edit — a structural feedback loop, not a one-off glitch. - One-shot lift: a
[TTS-TIME:HH:mm]marker whose moment already passed lifts the alarm to now+20 minutes once at record creation (now+5 when +20 crosses midnight, now at the 23:59 edge); from then on the renderer writes exactly the authored instant. - Past alarms are never repainted: an alarm whose moment has passed stays exactly as Microsoft holds it — no fake fresh alerts, and Microsoft's own recurrence engine is no longer fought on recurring tasks.
🔁 Adoption Contract Restored
- Renderer writes converge on a stable instant, so the "a difference means the user edited it" assumption in the Microsoft-side adoption path holds again — the feedback loop is structurally gone, with no behavior change to adoption itself.
📝 Docs & Tooling
- Docs refreshed: engineering audit header updated to v0.8.2 with the drift-fix summary, README test count updated, and the site version badge follows the release.
- CI stability: the baseline CLI test degrades gracefully when this dev machine's endpoint protector briefly blocks child-process spawning (EBUSY) — the same policy
validate.mjsalready follows.
✅ Test Suite & Verification
- 465 automated tests passing (100% PASS locally), including rewritten R-1 boundary tests for the authored-instant semantics and new regressions: the one-shot lift ladder (+20 / +5 / now), the no-repaint rule for past alarms, and second-pass renderer convergence.
- Static gate (
validate.mjs) and package dry-run validation pass; npm publish for 0.8.2 follows the maintainer's 2FA re-login.
v0.8.1
Tasks–To Do Sync v0.8.1
Fresh installs through the 3-step setup wizard now enable subtask and checklist synchronization by default (SYNC_ENABLE_SUBTASKS=true joins the setup defaults). Upgrading users are unaffected: explicit Script Properties are preserved, and the runtime fallback when the property is absent remains off. Google Calendar projection stays opt-in via SYNC_CALENDAR_PROJECTION=true.
What's Changed
🔀 Subtask Synchronization Enabled by Default for New Installs
- Rationale: the subtask engine carries 41 dedicated unit tests (three-way merge, conflict isolation, self-heal, deletion guards, matrix scenarios), live real-account verification with a clean 0-task-residue reset, and three released versions of field exposure (v0.6.0 → v0.8.0) — the "ship it dark because it is new" rationale no longer holds.
- Bounded blast radius preserved: the runtime fallback when the property is absent remains off, so an install that deliberately removes the property keeps task hierarchies untouched; the wizard's status report now surfaces all five defaults.
📝 Docs & Tooling
- Static validator pin extended:
validate.mjs(WO-14) now pins all five setup defaults, so a silent drift inPUBLIC_SETUP_DEFAULTSfails the gate. - Docs refreshed: README feature table, quick-start, the deployment defaults table (the subtask knob is now documented), and the engineering audit header updated to v0.8.1. The site version badge follows the published package.
✅ Test Suite & Verification
- 461 automated tests passing (100% PASS locally); the wizard-default fixtures were updated to cover the five-default shape, including the deliberate-override case (
allSafetySettingsValidwith mismatched defaults). - Static gate (
validate.mjs) passes; the E2E verification chain for v0.8.0 (scenarios 1–5a live on a disposable account, 6–7 unit-covered, 8 stress: 50 tasks with zero retries and zero quarantines) carries over unchanged — this release touches only wizard defaults and documentation.
v0.8.0
Tasks–To Do Sync v0.8.0
This release rebuilds Time Bridge around a canonical time record: time now lives in sync state as { date, time|null } — wall clock only, never an instant and never a time zone — and the marker, Microsoft writes, and Google Calendar projection become independent, idempotent renderers around it. Upgrading requires no new configuration: a v0.7.5 state loads unchanged, and the first v0.8.0 round rebuilds the record and drops the legacy keys.
What's Changed
⏱️ Time Bridge v3.0 — Canonical Time Record & Idempotent Renderers
- Time as a first-class citizen of sync state: each mapped pair carries
rec.td = { date, time|null }. The[TTS-TIME:HH:mm]marker is now an intake ticket — parsed into the record and spliced out, after which state is the truth. The marker grammar and the Gemini/voice capture flow are unchanged. - Single owner for
due: the ordinary three-way merge owns the due date and the whole{date, time}unit; the Microsoft renderer owns the time and the reminder. The former ownership carve-out is gone, which structurally removes the WO-7 class of bug (a due frozen out of the merge). - Five-stage journal deleted: the multi-stage Time Bridge journal, its dead-letter table, the ownership tri-state (
rec.rem.ms/msAt), the surrender triggers, and the event detectors are replaced by fingerprint comparison, an explicit adoption of hand-set Microsoft times, and a per-renderer retry ladder. - Live-account platform facts, now encoded: the Microsoft To Do API stores the due as a date only (any time component is dropped), and Graph ignores reminder fields when a PATCH also carries
dueDateTime— so the renderer writes the reminder and the due as separate PATCHes, and the reminder is the time carrier.
⏰ Reminder Policy: One Rule (R-1)
max(due instant, now + 20 minutes), in the project time zone, and no alarm at all when that moment would land on a different calendar day than the task date. The 23:35 cutoff and the 23:55 cap are deleted. Deliberate behaviour change: a task dated today whose time has passed now alarms at 23:5x instead of not ringing. Nothing ever writes an epoch (1970) sentinel.
🪜 Failure Handling Replaces the Journal (R-4)
- Per-renderer retry ladder: each renderer records
rec.td.retry.<name> = { fails, lastFailAt, quarantined }. The first failures retry every round, then once a day; after the slow lane also fails, the pair moves to the drawer and the operator receives one aggregated, de-identified notification (24h alert cooldown). Editing the task's marker or date revives it from a clean slate. The old unattended dead-letter drawer is emptied on the first upgrade round.
🔕 New Knob: SYNC_CALENDAR_PROJECTION_REMINDER (R-2, default true)
- Silent projection: with the knob set to
false, projected events still occupy their calendar slot but carry no popup reminder — for users whose Microsoft To Do alarm already covers the task (for example iPhone users with Apple Reminders connected to Microsoft and Apple Calendar subscribed to Google). Flipping the knob re-converges existing events one by one; the reminders-on fingerprint is byte-identical to v0.7.5, so upgrading does not re-patch existing events.
🧹 Fixes Found While Refactoring
- Dead-letter state validation (latent since v0.7.x): the dead-letter entry validator applied the plain journal key whitelist, so any state that had ever produced a dead-letter entry failed its own next load with
STATE_MALFORMED. No release had written one, so it was never observed. The whitelist now acceptsfailedAt/reason. validate.mjsrobustness: the CLI--versioncheck no longer aborts the whole gate on machines that refuse to spawn a child node process (EBUSY); the check is skipped with a warning instead.
✅ Test Suite & Verification
- 461 automated tests passing (100% PASS locally), including new suites for intake, the renderers and retry ladder, the
{date, time}merge unit, the projection reminder knob, and the v0.7.5 → v0.8.0 migration. Tests for the deleted mechanisms were replaced one for one, each named after the mechanism that now covers the behaviour. - Live end-to-end verification on a disposable account (
simon.tts.test@gmail.com): marker intake and splicing, Microsoft due/reminder convergence, calendar projection and completion cleanup,NONEclear-time flow (reminder wipe included), Microsoft-side time adoption, andSYNC_TIME_BRIDGE=falsebehaviour. A 50-task stress batch consumed every marker, built every time record, and completed with zero retries and zero quarantines. - Known scope notes:
time-bridge.gsships at 770 lines against the original ≤450 target (the preserved helper block plus the renderer set; documented deviation), and the live stress projection converges across rounds under the 20-ops-per-round budget.
v0.7.5
Tasks–To Do Sync v0.7.5
This stability and correctness release hardens the synchronization engine with a cross-time-zone fix for date-only due dates, a rebuilt list-deletion guard chain, and operator-visible conflict reporting. Upgrading requires one fresh Microsoft authorization because the requested scope set now includes MailboxSettings.Read; Google authorization and all stored state are untouched.
What's Changed
🗓️ Cross-Time-Zone Date-Only Due Fix
- Mailbox Time-Zone Discovery: The engine reads the Microsoft mailbox time zone (
MailboxSettings.Read) and requests Graph render due values in that authored zone, so date-only dues arrive as predictable local midnights at every project time zone. - Explicit Due Routing: A midnight value with no UTC offset is treated as a floating date; everything else is a real instant projected into the project time zone, with the fail-closed unknown-zone gate preserved.
- Safe Fallback: When the mailbox has never saved a time zone or the scope is missing, the engine keeps using the project time zone exactly like v0.7.0.
- Live Verification: The same
2026-09-25date-only task landed as2026-09-24on Google before the fix and2026-09-25after, with the project time zone set toAmerica/New_York.
🛡️ Deletion & Data-Integrity Guards
[TTS-TIME:NONE]Clear-Time Intent: The marker keeps the existing Microsoft date and only clearshasTimeplus reminder ownership locally. No epoch (1970-01-01) date or reminder is ever written to either side.- Hardened List-Deletion Chain:
- Loud Pagination Validation: A malformed provider page throws instead of returning a silently short inventory.
- Computed Inventory Completeness: The list-inventory flag is computed from a default-list canary instead of being hardcoded
true. - Missing-Side By-Id Probe: The missing side of a pair is re-read by id before any list deletion finalizes; a resurrected list blocks the deletion with
LIST_DELETE_MISSING_SIDE_RESURRECTED.
SYNC_TIME_BRIDGEOFF ReleaseshasTime: Disabling the knob now lets ordinary field merge re-own the due field instead of freezing it out of the merge forever.- Null-Prototype Exclusion Lookup: A list literally named
constructoris no longer phantom-excluded from synchronization.
⚡ Reliability & Observability
- Google 403 Quota Backoff:
rateLimitExceeded/quotaExceeded/userRateLimitExceeded(andRESOURCE_EXHAUSTED) now ride the 429 retry path instead of aborting the round on the first attempt. A bare 403 stays fatal. - Per-List 404 Isolation: A 404 while fetching one Microsoft list's tasks isolates that list instead of aborting the whole round.
- Field-Conflict Visibility:
healthCheck()andinspectSyncState()now report a boundedfieldConflictscount for quarantined conflicts. - Recurrence Alert Hygiene: The both-recurrence alert collapses newlines in task titles and reports opaque id previews instead of raw provider ids.
⚙️ Defaults & Setup
SYNC_CALENDAR_PROJECTIONNow Opt-In (defaultfalse): Google Calendar event projection is disabled by default; Time Bridge due-time and reminder synchronization is unchanged.- Subtask Synchronization Remains Opt-In (
SYNC_ENABLE_SUBTASKS=true): Documentation corrected to match the runtime default. - Advanced (Entra) Setups: Add the delegated
MailboxSettings.Readpermission alongsideTasks.ReadWrite. - Setup Wizard Checkmarks: Step-3 icons now toggle correctly in every browser; the earlier code relied on the SVG
hiddenproperty alone, which some browsers ignore.
✅ Test Suite & Verification
- 445 automated unit tests passing (100% PASS).
- Static validation pins the four install-time
PUBLIC_SETUP_DEFAULTSvalues and the release metadata. - Real-account live proofs recorded for every P0/P1 change, including the cross-time-zone A/B end-to-end run on the disposable test environment (
simon.tts.test@gmail.com).
v0.7.0
Tasks–To Do Sync v0.7.0
This major feature release introduces Time Bridge v2.4, enabling seamless bidirectional synchronization of task due times and reminders between Microsoft To Do and Google Tasks, complete with autonomous secondary Google Calendar projection and strict fail-closed safety protocols.
What's Changed
⏰ Time Bridge v2.4 (Due Time & Reminder Sync with Google Calendar Projection)
- Bidirectional Due Time & Reminder Synchronization: Bridges the architectural gap between Microsoft To Do's native reminder/due time support and Google Tasks' date-only API limitation.
- Fail-Closed Splicing Notes Marker (
[TTS-TIME:HH:mm]): Parses strict uppercase 24-hour time (00:01–23:59) from the first line of Google Tasks notes, automatically stripping the marker and preserving clean user notes. - Autonomous Google Calendar Projection: Projects 30-minute timed events with
start.dateTimeonto a dedicated secondary Google Calendar (Tasks-ToDo-Sync), making task due times visible on Google Calendar schedules without polluting user personal calendars. - Deterministic SHA-256 Event Indexing: Computes deterministic event IDs (
f211409b...) with zero negative byte corruptions and automated completion cleanup (deletes calendar projection when task is completed or removed). - Tri-State Field Ownership Protocol: Google-authored tasks with notes markers take precedence; Microsoft-authored tasks take precedence when
reminderDateTimeis updated in To Do. - Fail-Closed Safety Knobs:
SYNC_TIME_BRIDGE(defaulttrue): Master switch to enable/disable Time Bridge processing entirely.SYNC_CALENDAR_PROJECTION(defaulttrue): Toggle to enable/disable Google Calendar event projection while keeping To Do reminder synchronization active.
🧠 Google Gemini Integration Guide
- Added
docs/gemini-saved-info.mdcontaining prompt templates for Google Gemini Saved Info / Personalization to automatically generate[TTS-TIME:HH:mm]markers via natural voice/chat commands.
✅ Test Suite & Verification
- 427 automated unit tests passing (100% PASS).
- Packaging dry-run and smoke tests verified (28 files, 25 packaged release files).
- Real-account live acceptance probe verified on disposable test environment (
simon.tts.test@gmail.com).