Add open-task-manager command so users can access Task Manager without navigating to Settings.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
T2: Add TFile instanceof guards in openFile/getTaskLine, remove as any casts
T3: Filter deleteIssueTask catch to only ignore 404 errors
T4: Add isSyncingFromTodoist echo protection for Keep Todoist resolve
T7: Add _closed flag to prevent post-close DOM operations
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- loadAndRender(): Added loading indicator and single incrementalSync() call at modal open with warning banner on sync failure
- renderConflictedSection(): Replaced GetTaskById with getTaskByIdLocal for local lookup
- resolveConflict(): Replaced 3 GetTaskById calls with getTaskByIdLocal, preserved post-mutation incrementalSync()
This eliminates the N+1 network requests that prevented the modal from displaying all tasks.
Non-primary devices now exit onload() immediately after loading settings,
keeping only the settings tab and status bar. No scheduler, no event
handlers, no API connections, no commands registered.
- onload: check deviceId vs primaryDeviceId before initializePlugin()
- onload: skip deviceId generation when no API token (fresh install safety)
- initializePlugin: auto-claim primary when primaryDeviceId is empty
- Settings UI: button text 'Switch to Primary' + reload notice
Fixes multi-device bug where Obsidian Sync copies data.json across devices,
causing all devices to claim primary role. Now stores the primary device's
ID string and compares against local device-id file (not synced).
- Settings: isPrimaryDevice:boolean → primaryDeviceId:string (default empty)
- Migration: auto-migrates isPrimaryDevice=true → primaryDeviceId=<deviceId>
- GHOST_FIELDS: strips legacy isPrimaryDevice from loaded data
- main.ts: cachedDeviceId property + synchronous isPrimaryDevice() helper
- toTodoist.ts: 6 push guards use isPrimaryDevice() method
- Settings UI: toggle replaced with 'Claim as Primary' button + status display
- checkModuleClass: added deviceManager to guard list
- Extract all inline styles to styles.css with tm-* class namespace
- Add structured header with summary badges (conflict/issue/inactive counts)
- Improve conflict diff table with change markers and column headers
- Add proper empty state with icon and message
- Use card-based layout with header/body/actions sections
- Add hover effects on table rows, proper button styles
- Consistent spacing, typography, and Obsidian theme variable usage
deletedTaskCheck now calls saveSettings() internally when tasks are deleted,
so the outer if (deletedCount > 0) { saveSettings() } in onKeyUp was a
double-save. Remove it.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
deletedTaskCheck: replace Promise.all parallel map with for...of serial loop;
each task deletion immediately calls deleteTaskFileMapping then continues to
the next — no more partial-state windows.
fullTextNewTaskCheck: rewrite as per-task atomic cycle — AddTask → setMapping
→ CloseTask (if completed) → vault.modify → saveSettings → vault.read before
next iteration. Rollback (deleteTask + deleteMapping) on any per-task failure.
isProcessingModify guard prevents vault.modify from re-triggering the handler.
Also removes the unused deleteTasksByIds method (zero callers).
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Remove the fire-and-forget incrementalSync() call inside executeCommands that
caused concurrent sync_token conflicts. Replace with an API-level lock using
_syncRunning/_syncDirty/_syncWaiters so concurrent callers queue up and dirty
requests are retried via do-while loop instead of being silently dropped.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
After lineContentNewTaskCheck creates a task, lineModifiedTaskCheck could fire before syncData was updated, causing GetTaskById to return undefined and the task to be incorrectly marked as 'issue'.
Three-layer defense:
1. Immediate incrementalSync after task creation
2. GetTaskById retries with incrementalSync on cache miss
3. lineModifiedTaskCheck skips tasks with mapping age < 30s
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
The field defaults to false, so after data.json loss or fresh install, sync was permanently blocked until manual database fix. Each task already has its own syncEnabled flag, making this global gate redundant.
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)
Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
RebuildCacheResult now returns tasksProcessed, conflictsCount, nonActiveCount,
issueCount, convertedCount, tasksWithoutIdCount. Fix Database button displays
a summary notice after rebuild so users can see exactly what was found.
🤖 Sisyphus via OhMyOpenCode
- Show Notice to user when conflicts are found during rebuild
(previously only logged to debug, user had no visibility)
- Use vault.read() instead of cachedRead() in scanVaultTasks so
rebuild always reads fresh disk content, not stale memory cache
- Warn to console when duplicate taskId found across files instead
of silently dropping the second occurrence
- Downgrade legacy ID conversion failure from error to warn and
include note that affected tasks will retry on next rebuild
🤖 Sisyphus via OhMyOpenCode
When Vault and Todoist content/status match (active), write the
Todoist updated_at into the cache entry. This prevents toObsidian
from treating every task as changed after a rebuild, which would
cause unnecessary full pulls and risk overwriting local edits.
Conflicted/nonActive/issue entries intentionally leave updated_at
unset since syncEnabled=false already guards them.
🤖 Sisyphus via OhMyOpenCode
ast-grep metavariable $$$ was written literally instead of being
expanded during the previous refactor, causing ReferenceError at runtime.
Restored original log message strings for all 8 affected call sites.
🤖 Sisyphus via OhMyOpenCode
Replace all console.log calls in main.ts and src/ with this.debugLog() /
this.plugin.debugLog() so debug output is suppressed unless Debug Mode
is enabled in settings.
🤖 Sisyphus via OhMyOpenCode
Previously, Fix Database would force-enable or force-disable syncEnabled based on check results. Now the master sync switch is fully user-controlled and independent of database health.
Database issues are now shown as a non-blocking warning appended to the status text instead of replacing it. This allows users to:
- Keep sync enabled even with database issues (at their own risk)
- Disable sync independently of database health
- See both sync status and database warnings simultaneously
Changes:
- Remove syncEnabled updates from database check/fix operations
- Append database warning to status text instead of replacing it
- Update user-facing notices to remove sync enable/disable messaging
🤖 Sisyphus via OhMyOpenCode
<!-- OMO_INTERNAL_INITIATOR -->
Update the database check report generator to display file paths as clickable Obsidian wiki links ([[path|filename]]) in the issues table, enabling users to navigate directly to affected files from the report.
🤖 Sisyphus via OhMyOpenCode
<!-- OMO_INTERNAL_INITIATOR -->
Adds a new 'Verify Database' button in the settings UI that:
- Runs checkDatabase() in read-only mode
- Displays task counts from Todoist and vault mappings
- Shows health status (✅ Healthy or ⚠️ N issues)
- Generates and links to a detailed report
- No data is modified during verification
🤖 Sisyphus via OhMyOpenCode
<!-- OMO_INTERNAL_INITIATOR -->