Skip to content

Group issue statuses the way the Linear app does - #271

Merged
schpetbot merged 1 commit into
mainfrom
issue-status-order
Aug 31, 2026
Merged

Group issue statuses the way the Linear app does#271
schpetbot merged 1 commit into
mainfrom
issue-status-order

Conversation

@schpetbot

Copy link
Copy Markdown
Collaborator

Fixes the status ordering you reported in issue mine. Closes CLI-32.

What was wrong

issue mine --all-states listed Canceled → Done → Backlog → Todo — the reverse of the app:

CLI-63   Canceled
CLI-24   Done
CLI-90   Backlog
CLI-113  Todo

The cause was workflowState: { order: "Descending" }, copy-pasted across four call sites covering issue mine, issue query, and issue start. The commit that introduced it (592000f) recorded only "sorts by workflow state first"; no rationale for the direction survives.

Why flipping the direction wasn't the fix

Measured against the live API, Ascending returns Todo before Backlog — so neither direction matches a team's configured order. Linear sorts by an internal ranking, and WorkflowStateSort offers no way to sort by position at all.

The schema says what the app actually does: position orders states "in ascending order of position within their type group". So the key is (type group, position), and the ordering has to happen client-side.

That distinction is load-bearing. This workspace has an In Review at position 1002 with type started — under a raw-position sort it lands after Duplicate instead of beside In Progress. Which is exactly what linear team states has been printing:

 Done        completed          In Progress started
 Canceled    canceled     →     In Review   started
 Duplicate   duplicate          Done        completed
 In Review   started            Canceled    canceled

Same root cause reached through getWorkflowStates(); both now share one comparator.

Design notes

position is selected on each issue's own state rather than looked up per team — no extra round trip, and multi-team results are automatically correct. But position is only meaningful within a team, so the key is chosen once per result set:

  • single-team listing → type group, then configured position
  • multi-team listing → type group only, leaving the server's priority order standing within each group

Ranking one team's positions against another's compares unrelated numbers, and deciding it per-pair wouldn't even be transitive (A(teamA,5) == B(teamB,0), B == C(teamA,0), yet A > C), which would break the comparator contract.

Search is untouched — it's relevance-ranked, and regrouping by status would destroy the ordering that is the point of the command.

Two behavior changes worth a look

  1. The server sort is now Ascending, which changes membership under --limit, not just order. Previously a truncated --all-states listing filled up with canceled issues before reaching any open work. It now keeps the most actionable ones. Since the API can't sort by position, it still decides which issues are fetched; the client only reorders them.
  2. A non-finite position now throws rather than being tolerated. NaN compares as "equal", so it would quietly degrade a listing to some other order. That guard immediately caught two stale test fixtures.

Verification

578 tests pass; deno task check, deno lint, deno fmt --check clean. New regression tests cover all seven type groups, the In Review case, a low-position canceled state that must not jump to the top, an unrecognized future state type, and multi-team grouping. queryIncludes: "position" pins the GraphQL selection, since the mock echoes fixtures verbatim and a snapshot alone can't prove the query asked for the field.

QA'd live against the workspace: default issue mine, --all-states, --sort manual, --limit truncation, issue query --json (connection shape intact, state.position added), issue query --search --json (no position, relevance preserved), issue start, and team states.

@linear

linear Bot commented Aug 31, 2026

Copy link
Copy Markdown

CLI-32

`issue mine --all-states` listed Canceled and Done first and Backlog and Todo
last -- the reverse of the app. The primary sort was
`workflowState: { order: "Descending" }`, copy-pasted across four call sites
covering `issue mine`, `issue query`, and `issue start`. The commit that added
it (592000f) recorded only "sorts by workflow state first"; no rationale for the
direction survives.

Simply flipping the direction would not have fixed it. Measured against the API,
`Ascending` returns Todo before Backlog, so neither direction matches a team's
configured order -- Linear sorts by an internal ranking, and `WorkflowStateSort`
offers no way to sort by position at all. The schema says what the app actually
does: position orders states "in ascending order of position within their type
group". So the key is (type group, position), and ordering has to happen
locally.

That distinction is load-bearing rather than pedantic. This workspace has an
"In Review" at position 1002 with type `started`; under a raw-position sort it
lands after "Duplicate" instead of beside "In Progress" -- which is exactly what
`linear team states` has been printing, the same bug reached through
`getWorkflowStates()`. Both now share one comparator.

`position` is selected on each issue's own state rather than looked up per team,
so there is no extra round trip and multi-team results are automatically
correct. It is only meaningful within a team, though, so the key is chosen once
per result set: a single-team listing sorts by type group then position, while a
multi-team one sorts by type group alone and leaves the server's priority order
standing inside each group. Ranking one team's positions against another's would
compare unrelated numbers, and deciding it per-pair would not even be transitive.

Two consequences worth flagging. The server sort is now `Ascending`, which under
`--limit` changes which issues are fetched, not just their order: previously a
truncated `--all-states` listing filled up with canceled issues before reaching
any open work. And a non-finite position now throws instead of being tolerated,
because a NaN comparator result reads as "equal" and would quietly degrade the
listing to some other order -- that guard caught two stale test fixtures the
moment it went in.

Search is untouched; it is relevance-ranked, and regrouping it by status would
destroy the ordering that is the point of the command.
@schpetbot
schpetbot merged commit 3960333 into main Aug 31, 2026
15 checks passed
@schpetbot
schpetbot deleted the issue-status-order branch August 31, 2026 23:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants