Skip to content

fix: support sections in filter views - #472

Merged
gnapse merged 3 commits into
mainfrom
ernesto/fix-filter-view-sections
Aug 11, 2026
Merged

fix: support sections in filter views#472
gnapse merged 3 commits into
mainfrom
ernesto/fix-filter-view-sections

Conversation

@gnapse

@gnapse gnapse commented Aug 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • split saved comma-separated filters into API-supported section queries
  • fetch sections concurrently and preserve section order in human, JSON, and NDJSON output
  • add regression coverage and update the agent command reference

Closes #274

Why

Todoist saved filters use commas to display queries as separate lists. However, the public Get Tasks By Filter API documentation states: “Multiple filters (using the comma , operator) are not supported.”

The CLI passed the full saved query to that endpoint, which returned a successful empty result. This change splits the saved query and calls the endpoint once per section.

Behavior and deliberate simplifications

  • Each non-empty section has an independent API request and cursor stream.
  • Sections run concurrently through a worker pool capped at six active section streams. This limits request bursts while keeping common filters fully parallel.
  • Pages within one section remain sequential because each page needs the previous cursor.
  • Results preserve the saved section order even if responses complete out of order.
  • Parsing is deliberately limited to finding unescaped commas. It is not a full Todoist query parser.
  • A comma preceded by an odd number of backslashes is preserved as a literal. Escapes remain in the subquery sent to the API.
  • Quotes and parentheses do not protect unescaped commas. Todoist documents an unescaped comma as the separate-list operator and backslash as the escape mechanism.
  • Empty fragments, such as the middle fragment in today,,tomorrow, are ignored.
  • Results are not deduplicated. The same task can intentionally appear in more than one section.
  • If any section request fails, the whole command fails instead of returning output that looks complete.
  • Empty multi-section results do not load unrelated project or collaborator data.
  • --limit applies independently to each section.
  • --cursor is rejected for multi-section filters because one cursor cannot represent several independent streams. Use --all, or query one section directly. Structured output includes nextCursor per section.
  • Multi-section JSON uses { sections: [{ query, results, nextCursor }] }. NDJSON emits one section object per line. Single-section output stays unchanged.

Validation

  • Reproduced with an isolated saved filter containing three comma-separated queries. Before the fix, the saved filter returned zero tasks while each component query returned one task.
  • Re-ran the same saved filter with the local build. Human, JSON, and NDJSON output each returned three sections with one expected task per section.
  • Removed the temporary filter, project, and tasks after validation.

Tests

  • npm run type-check
  • npm run check
  • npm run check:skill-sync
  • npm test — 1,795 tests pass

@doistbot doistbot left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR fixes support for Todoist's comma-separated saved filters by splitting them into individual API requests per section, with concurrent fetching and order-preserving output across human, JSON, and NDJSON formats.

Few things worth tightening:

  • Skip the project/cache lookup in the multi-section path when all sections return no tasks — currently an empty multi-section filter still calls getProjects(), and if that unrelated request fails, the command errors instead of cleanly rendering the empty results.

I also included a few optional follow-up notes in the details below.

Optional follow-up notes (5)
  • P3 src/commands/filter/filter.test.ts:507: Reuse fixtures.tasks.basic (with per-test overrides) for these new task responses instead of hand-building partial task objects. The shared fixture supplies a valid SDK task shape and keeps test data consistent; the same applies to the new literals in this test block.
  • P3 src/commands/filter/view.ts:79: formatFilterSectionsJson and formatFilterSectionsNdjson share an identical sections.map((section) => ({ query, results: section.results.map(task => processJsonItem(...)), nextCursor })) body — only the outer wrapper (formatJson({ sections }) vs formatNdjson(...)) differs. Extract the shared mapping into a small helper (e.g. mapFilterSections(sections, full, showUrls)) so the two callers don't drift apart if the section payload shape changes.
  • P3 src/commands/filter/view.ts:134: Move this flattening below the JSON/NDJSON early returns. Structured output only reads sections, so this creates an unused second array of every task; with --all on a multi-section filter that can be a large allocation.
  • P3 src/commands/filter/view.ts:107: Promise.all fans out all section requests concurrently with no bound. A filter with many comma-separated queries — say, 20+ — would fire that many concurrent API calls, each of which may paginate further. While uncommon in practice, adding a modest concurrency cap (e.g., 5–10) with a simple semaphore or a library like p-limit would prevent request bursts without meaningfully hurting latency for the common case of 2–5 sections.
  • P3 src/commands/filter/filter.test.ts:630: No test covers the documented behavior that empty fragments like the middle fragment in today,,tomorrow are ignored. The exported splitFilterQueries function explicitly skips empty trimmed sections (lines 37-38 in view.ts), and the PR description calls this out as intentional. A naive refactor (e.g., using query.split(',')) would regress silently — users would see empty section headers in their output. Consider adding a unit test for splitFilterQueries covering empty fragments and/or the odd-backslash escape rule, since the function is exported and has documented edge cases not exercised by the integration tests.

Share FeedbackReview Logs

Comment thread src/commands/filter/view.ts
@gnapse gnapse self-assigned this Aug 10, 2026
@gnapse gnapse added the 🙋 Ask PR PR must be reviewed before merging label Aug 10, 2026
Comment thread skills/todoist-cli/SKILL.md Outdated
Co-authored-by: Scott Lovegrove <scott@ferretlabs.com>
@gnapse
gnapse force-pushed the ernesto/fix-filter-view-sections branch from 23b479e to 2757576 Compare August 11, 2026 12:58
@gnapse
gnapse merged commit 7ac89ca into main Aug 11, 2026
6 checks passed
@gnapse
gnapse deleted the ernesto/fix-filter-view-sections branch August 11, 2026 13:00
@doist-release-bot

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.1.8 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🙋 Ask PR PR must be reviewed before merging released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Todoist view filter doesn't show tasks when filter query consists of multiple comma-separated queries

3 participants