Skip to content

feat(filter): sort tasks the way the Todoist apps do - #479

Open
craigcarlyle wants to merge 1 commit into
mainfrom
craigcarlyle/fix-issue-473
Open

feat(filter): sort tasks the way the Todoist apps do#479
craigcarlyle wants to merge 1 commit into
mainfrom
craigcarlyle/fix-issue-473

Conversation

@craigcarlyle

@craigcarlyle craigcarlyle commented Aug 17, 2026

Copy link
Copy Markdown

Summary

  • td filter view now orders tasks the way the Todoist apps order them, instead of printing whatever order the API returned
  • Reads the sorting saved on the filter's view with api.getViewOptions() and orders the results with sortTasks, both from @doist/todoist-sdk@14.1.0
  • Falls back to Todoist's default hierarchy when the view has no saved sort, which is what "Manual (default)" means in the app
  • Adds --sort default|priority|date|deadline|date-added|name|project|assignee|workspace|none and --sort-order asc|desc, resolved flag first, then saved view, then Todoist default
  • --sort none returns the raw API order
  • Sorts each section of a comma-separated filter on its own, and returns --json / --ndjson in the same order as the pretty output
  • Prints the resolved sort in the view header, under Query: and URL:

Why the order was wrong

  • Todoist doesn't sort server-side. GET /tasks/filter returns storage order, roughly date added ascending, which is why the reporter found that setting their filter to "date added asc" made the app match the CLI
  • Every client sorts locally, by sorted_by on the view and then by a documented default
  • The CLI read neither piece

What stays in the CLI

@gnapse put the comparators and the view-options reader in the SDK (Doist/todoist-sdk-typescript#665 and #666), so this PR carries only what sortTasks asks its caller for:

  • buildProjectOrder, which lays the sidebar out as the projectOrder map the SDK wants. It has no helper for this, so every consumer builds its own
  • queryUsesDates, which decides between the priority-first and date-first hierarchies from the filter query
  • The vocabulary --sort speaks, and the mapping from a saved view onto it

The ordering tests here cover that handoff. The comparators are tested in the SDK, so re-testing them through this wrapper would only break on his refactors.

Not addressed

Test plan

  • 1861 tests pass, plus type-check, lint, format and SKILL.md sync, all against the published 14.1.0
  • Manual: a filter whose view is saved with sorted_by: PRIORITY renders p1 first
  • Manual: default-sorted filter with date queries leads with date, priority breaks the tie
  • Manual: --sort none reproduces the pre-fix order
  • Manual: --sort assignee across a workspace filter with 13 assignees sorts A-Z with unassigned last, and --sort-order desc reverses it

Closes #473

@doistbot

This comment was marked as outdated.

@craigcarlyle
craigcarlyle force-pushed the craigcarlyle/fix-issue-473 branch from 712c7b8 to f2ed126 Compare August 17, 2026 18:10
@craigcarlyle
craigcarlyle changed the base branch from main to craigcarlyle/task-sort August 17, 2026 18:11
@craigcarlyle
craigcarlyle force-pushed the craigcarlyle/fix-issue-473 branch from f2ed126 to 627fe70 Compare August 17, 2026 18:30
@craigcarlyle craigcarlyle self-assigned this Aug 17, 2026
@craigcarlyle craigcarlyle added the 🙋 Ask PR PR must be reviewed before merging label Aug 17, 2026
@gnapse
gnapse force-pushed the craigcarlyle/task-sort branch from 835a2ae to 0c56b60 Compare August 18, 2026 12:19
@gnapse
gnapse force-pushed the craigcarlyle/fix-issue-473 branch from 627fe70 to 7f7b859 Compare August 18, 2026 12:19
@craigcarlyle
craigcarlyle force-pushed the craigcarlyle/fix-issue-473 branch from 7f7b859 to e774af4 Compare August 18, 2026 15:36
@craigcarlyle
craigcarlyle force-pushed the craigcarlyle/task-sort branch from 0c56b60 to 79b8ebb Compare August 18, 2026 15:36
@craigcarlyle
craigcarlyle changed the base branch from craigcarlyle/task-sort to main August 18, 2026 16:10
@craigcarlyle
craigcarlyle force-pushed the craigcarlyle/fix-issue-473 branch from e774af4 to 1ea8879 Compare August 18, 2026 16:10
@craigcarlyle
craigcarlyle marked this pull request as draft August 18, 2026 16:24
@craigcarlyle
craigcarlyle force-pushed the craigcarlyle/fix-issue-473 branch 3 times, most recently from fe760d7 to c74a731 Compare August 19, 2026 19:36
@craigcarlyle
craigcarlyle requested a review from gnapse August 19, 2026 19:51
@craigcarlyle
craigcarlyle marked this pull request as ready for review August 19, 2026 19:51

@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.

td filter view now sorts tasks to match the Todoist apps, reading the saved view sort via the SDK and adding --sort/--sort-order flags, with graceful fallbacks and JSON/NDJSON parity.

Few things worth tightening:

  • Rebase onto current main — this branch downgrades the open dependency (and its transitive deps) and reverts version metadata, both unrelated to the feature.
  • fetchProjects() only fetches the first page; when projects are paginated, later projects won't appear in projectOrder/workspaceOrder, breaking default/project/workspace sorts. Fetch all pages before building the order map.
  • Don't swallow getViewOptions() errors into an empty list — a transient API/auth failure silently renders a saved sort in default order. Let the error propagate; a genuinely empty response already handles the no-saved-sort case.
  • queryUsesDates only strips the first word of named references, so a project like #due date leaves date behind and triggers the wrong hierarchy. Consume the full named-reference operand before applying the date pattern.
  • --sort none still clones every section array through sortTasks; skip the sort block entirely when the field is none to avoid the unnecessary copy.

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

Optional follow-up notes (4)
  • P3 src/lib/task-sort.ts:57: This comment says default has no field, but SORTED_BY_FIELD['default'] is 'MANUAL' (via FIELD_BY_SORTED_BY.MANUAL === 'default'); only none is absent. A reader would expect sortTasks to pass sortedBy: null for the default sort when it actually passes 'MANUAL'. Update the comment to reflect the actual mapping.
  • P3 src/commands/filter/filter.test.ts:1107: The test name says the view options "cannot be read," but this mocks getViewOptions resolving to [] (a valid empty response), so loadViewOptions' catch branch — the logging + return [] fallback — is never exercised. Use mockApi.getViewOptions.mockRejectedValue(new Error('boom')) to actually cover that path, or rename the test to describe the empty-response case.
  • P3 src/lib/task-sort.test.ts:266: These assertions cover a branch that can't be reached through the CLI: --sort/--sort-order are registered with withCaseInsensitiveChoices, which sets Commander choices, so invalid values are rejected during parsing before parseTaskSortField/parseTaskSortDirection ever run. The CliError throws are dead code in the command path; either drop the defensive validation (and this test) or note that it's only guarding the exported helper, not user-facing behavior.
  • P3 src/lib/task-sort.test.ts:276: sortNeedsProjects and sortNeedsCollaborators are one-line predicates (field !== 'none' and field === 'assignee') whose observable behavior — whether getProjects / collaborators are fetched — is already verified by the integration tests in filter.test.ts ('fetches projects in JSON mode' and 'skips the project fetch when nothing is sorted'). These unit tests re-state the implementation without adding regression signal.

Share FeedbackReview Logs

Comment thread package.json Outdated
Comment thread src/commands/filter/view.ts
Comment thread src/commands/filter/view.ts
Comment thread src/lib/task-sort.ts Outdated
Comment thread src/commands/filter/view.ts Outdated
`td filter view` printed tasks in whatever order the API returned. Todoist
doesn't sort server-side: every client applies the sorting saved on the view,
and falls back to a documented default hierarchy when the view has none, which
is what "Manual (default)" means in the app. The CLI read neither piece, so a
saved filter listed in an order no other Todoist client shows.

Reads the saved options with `api.getViewOptions()` and orders the results with
`sortTasks`, both added to the SDK by @gnapse in
Doist/todoist-sdk-typescript#665 and #666. What lives here is the part the SDK
asks the caller to supply: the vocabulary `--sort` speaks, the mapping from a
saved view to that vocabulary, the sidebar layout it wants as a lookup, and the
guess at whether a filter query is date-driven, which picks between the
priority-first and date-first hierarchies.

Adds `--sort default|priority|date|deadline|date-added|name|project|assignee|
workspace|none` and `--sort-order asc|desc`, resolved flag first, then saved
view, then Todoist default. `--sort none` returns the raw API order. Each
section of a comma-separated filter sorts on its own, and `--json` / `--ndjson`
come back in the same order as the pretty output.

Closes #473

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@craigcarlyle
craigcarlyle force-pushed the craigcarlyle/fix-issue-473 branch from c74a731 to fdbe654 Compare August 19, 2026 20:03
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: sorting via views is not working by default

2 participants