Skip to content

Filter the account-wide task listings by assignee and due date - #612

Merged
jeremy merged 1 commit into
mainfrom
account-wide-task-filters
Aug 4, 2026
Merged

Filter the account-wide task listings by assignee and due date#612
jeremy merged 1 commit into
mainfrom
account-wide-task-filters

Conversation

@jeremy

@jeremy jeremy commented Aug 4, 2026

Copy link
Copy Markdown
Member

Adds two filters to the account-wide task listings:

  • --assignee — repeatable, server-side (assignee_ids[])
  • --duewith / without / overdue, server-side (due=)

Both ride the bounded walk reworked in #590 rather than reintroducing an
unbounded account-wide fetch.

This targets main directly. It is an ordinary PR, not part of a stack:
#611 merged as ebeecc13 (tree-identical to its reviewed head ad39cd8c), and
#610 was closed as superseded. Earlier revisions of this description said it was
stacked on bc5-command-surface and received almost no CI — both are obsolete.

Review findings, fixed

Explicit --due= was treated as omission. Every check tested the flag's
value, so an empty string was indistinguishable from never passing the flag:
it slipped past the project-scoped guard, built no account-wide filter, and
returned a full unfiltered listing to a caller who believed they had narrowed
it. Now rejected on presence (Flags().Changed). --assignee= had the
mirror-image bug — StringArrayVar appends the empty string, so len(...) > 0
sent a filter naming nobody — and is rejected the same way.

Filter usage errors demanded an account. The --due token check lived
inside the account-wide path, which runs after ensureAccount. With nothing
configured, todos list --due tomorrow hit account resolution first: an
interactive session got the account picker, a noninteractive one got
--account is required, and the actual mistake was never reported. Both the
token and emptiness checks now run in validateTaskFilterValues before account
resolution — neither depends on the account or the scope.
TestTaskFilterUsageErrorsPrecedeAccountResolution uses an app with no
configured account and asserts on the message; asserting only "no requests
were made" would have passed before the fix, since account resolution fails
without issuing one.

The pagination claim was wrong in four places. accountwide.go, todos.go,
cards.go and the test file header all said filtering leaves the page/request
count untouched. The production soak disproved that: 2 requests for 100
unfiltered todos vs 3 for --assignee's 21, because the bounded walk's cap
counts items, so a narrower result can need another page. Only the walk's
algorithm is unchanged — which is what the same-fixture test actually pins;
its body already said so correctly while the header contradicted it.

Four skill/annotation contradictions. The todos list agent note still said
--assignee requires a project and redirected cross-project work to reports assigned, and claimed --assignee works on todos "not cards" — which this PR
makes false too. The skill separately said "Cards do NOT support --assignee"
in two places (true project-scoped, false account-wide now), and Smart Defaults
advertised --due tomorrow natural dates — valid when setting a due date,
rejected on a listing, where --due is a different flag taking
with|without|overdue. All corrected so the skill presents one contract.

Also fixed earlier: the four (cross-project) recipes that omitted
--all-projects. accountWide := flags.allProjects || !projectKnown(...), so a
configured default project counts as in scope — those recipes returned different
results depending on the reader's config.

Verification at 20506d70

  • Full remote suite on the exact head: 22 success / 2 skipped / 1 neutral /
    0 failures
    .
  • Local bin/ci exit 0 on the same SHA.
  • Every behavioural guard is mutation-checked: neutering it fails exactly its
    own test and nothing else.
  • Zero unresolved review threads.

Copilot AI review requested due to automatic review settings August 4, 2026 00:26
@github-actions github-actions Bot added commands CLI command implementations tests Tests (unit and e2e) skills Agent skills docs labels Aug 4, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@jeremy

jeremy commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

CI coverage caveat for this stacked PR

test.yml triggers on pull_request: branches: [main], and this PR targets a
stack branch rather than main. So the green checks list here is not the
full suite — only label and the sensitive-change gate actually ran. Tests,
Lint, Race Detection, CLI Surface Check and the Nix build did not.

Substituted verification:

  • bin/ci run locally at this exact head — exit 0 — which is the same set
    of checks remote CI runs (fmt, vet, lint, unit tests, e2e, naming, surface
    snapshot, skill drift, smoke coverage, provenance, tidy).
  • The full remote suite will run automatically once this PR is retargeted to
    main as the stack lands.

Flagging it so the checks list is not read as more than it is.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 642857c6d5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread skills/basecamp/SKILL.md Outdated
Copilot AI review requested due to automatic review settings August 4, 2026 01:05
@jeremy
jeremy force-pushed the account-wide-task-filters branch from 642857c to 5249c1c Compare August 4, 2026 01:05
@jeremy jeremy mentioned this pull request Aug 4, 2026
@jeremy

jeremy commented Aug 4, 2026

Copy link
Copy Markdown
Member Author

Rebased onto the corrected #611 head (4577db37), and the four scope-unsafe
skill recipes review flagged are fixed in 5249c1ce.

The skill defect

Three recipes labelled (cross-project) omitted --all-projects:

  • todos list --assignee "Ann"
  • todos list --assignee ann --assignee bob
  • todos list --due without

plus the narrative at the bottom of the assignments section, which repeated the
mistake — it wrote todos list --assignee <person> without the flag while
correctly writing cards list --all-projects --assignee <person> right beside
it.

This is not cosmetic. todos.go computes
accountWide := flags.allProjects || !projectKnown(app, flags.project), so a
configured default project counts as in scope. For a reader with one
configured, those recipes silently degrade --assignee to a client-side filter
over that single project, and --due is rejected outright as account-wide-only.
The same recipe returns different results depending on the reader's config,
which is the worst failure mode for an agent-facing skill.

All four now carry --all-projects, and I added an explicit note stating that
--all-projects is what overrides a configured default and why omitting it is
config-dependent. I swept the rest of both skills: the only other
--assignee uses are --in <project> recipes explicitly labelled "(in
project)", which are correct as written.

Note on CI

#611 now targets main, so it gets the full remote suite. This PR still targets
bc5-command-surface, so it does nottest.yml is
pull_request: branches: [main]. bin/ci is exit 0 at this head, and I'll
retarget to main once #611 merges so the full suite runs before this is
considered mergeable.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5249c1ced3

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/commands/accountwide.go
Comment thread internal/commands/todos.go

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI review requested due to automatic review settings August 4, 2026 02:24
@jeremy
jeremy force-pushed the account-wide-task-filters branch from 5249c1c to 7fe6753 Compare August 4, 2026 02:24
Base automatically changed from bc5-command-surface to main August 4, 2026 02:27

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7fe6753666

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread skills/basecamp/SKILL.md
@jeremy
jeremy force-pushed the account-wide-task-filters branch from 7fe6753 to 7cb71fc Compare August 4, 2026 02:31

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (2)

internal/commands/accountwide.go:310

  • This loop duplicates resolveAssigneeFilterIDs (todos.go:860), which this same PR introduces to do exactly this: iterate the repeatable --assignee values and resolve each via resolvePersonRoleIDs(ctx, app, assignee, "Assignee"). Reusing it keeps the two assignee-resolution paths in sync (both files are in package commands). Optional, but avoids a second copy of the same logic drifting from the first.
	for _, assignee := range assignees {
		// Each value may itself be a comma-separated list, so --assignee is
		// repeatable and comma-separated both, matching how the other
		// people-taking flags already behave.
		ids, err := resolvePersonRoleIDs(ctx, app, assignee, "Assignee")
		if err != nil {
			return nil, err
		}
		filters.AssigneeIDs = append(filters.AssigneeIDs, ids...)
	}

internal/commands/accountwide.go:195

  • This comment points readers to filterTodosByAssignees, but no such function or note exists in the codebase. The project-scoped assignee logic lives in todoMatchesAnyAssignee (and resolveAssigneeFilterIDs) in todos.go. The dangling reference will mislead anyone who tries to follow it.

This issue also appears on line 301 of the same file.

// Project-scoped --assignee is a different animal — see the note on
// filterTodosByAssignees.

Copilot AI review requested due to automatic review settings August 4, 2026 02:32

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7cb71fcd86

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread skills/basecamp/SKILL.md

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (1)

internal/commands/accountwide.go:311

  • Optional (maintainability): this loop re-implements the exact assignee-resolution that resolveAssigneeFilterIDs (in todos.go) already provides. Reusing that helper keeps a single place that resolves the repeatable/comma-separated --assignee into IDs. This is wire-safe: the SDK marks AssigneeIDs omitempty (see internal/tui/workspace/data/hub.go:1009), so a --due-only call still sends no assignee_ids[].
	filters := &basecamp.EverythingTaskFilters{Due: due}
	for _, assignee := range assignees {
		// Each value may itself be a comma-separated list, so --assignee is
		// repeatable and comma-separated both, matching how the other
		// people-taking flags already behave.
		ids, err := resolvePersonRoleIDs(ctx, app, assignee, "Assignee")
		if err != nil {
			return nil, err
		}
		filters.AssigneeIDs = append(filters.AssigneeIDs, ids...)
	}
	return filters, nil

Copilot AI review requested due to automatic review settings August 4, 2026 02:43
@jeremy
jeremy force-pushed the account-wide-task-filters branch from 7cb71fc to 4c07e98 Compare August 4, 2026 02:43

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4c07e986e1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread internal/commands/todos.go

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (1)

internal/commands/accountwide.go:195

  • This comment points readers to "the note on filterTodosByAssignees", but no such function exists anywhere in the package. The project-scoped --assignee behavior it references is actually documented on listTodosInList (see the "same spelling, very different cost" note in todos.go), with the any-match semantics in todoMatchesAnyAssignee. The dangling name will send future maintainers searching for a symbol that isn't there.
// Project-scoped --assignee is a different animal — see the note on
// filterTodosByAssignees.

This is the feature the v0.12.0 signature change exists for. --assignee and
--due map onto EverythingTaskFilters, which 11 of the 16 aggregate methods
now accept.

--assignee is repeatable (and comma-separated within a value) and matches a
task assigned to any of the named people. On todos it already existed
project-scoped and was *rejected* account-wide, because the aggregates had
no parameter to map it onto; that rejection is gone. On cards it did not
exist at all — the group's agent note said so — and is added account-wide
only.

The same flag now means two different things by scope, and the help text
says so rather than papering over it. Account-wide it is a real
assignee_ids[] query parameter: the server narrows before it paginates, so
the filter never turns the bounded walk into a full crawl. Project-scoped
there is no server-side assignee parameter at all, so it filters
client-side over a deliberately unlimited fetch. Same results, very
different cost. The semantics are matched on purpose — the project-scoped
filter now matches any of the named people, the way assignee_ids[] does.

Note what the bounded walk does *not* promise: identical request counts.
The cap counts items, so a narrower filter returns fewer per page and can
need one more page to reach it. Soaked against account 2914079, unfiltered
took 2 requests for 100 todos and --assignee took 3 for its 21. The test
pins the invariant that actually matters — given identical page contents,
filtering does not change the walk.

Widening todos' --assignee from StringVar to StringArrayVar changes its
.surface type line. TestSurfaceSnapshot compares whole lines, so the old
one reads as a removal and is acknowledged in .surface-breaking.

--due takes with, without, or overdue. These are category tokens rather
than dates, so internal/dateparse is deliberately not involved.

Two combinations are refused before any request, and the tests assert the
transport stays untouched:

--assignee with --unassigned is necessarily empty. The server builds that
selector as todos_recordings.remaining.not_assigned over a relation the
assignee filter has already narrowed, so nothing can satisfy both. Answered
rather than refused, it would return zero rows that look like a real
answer.

--due with --overdue or --no-due-date asks two endpoints for one answer:
each of those selects its own listing on the same axis --due narrows.

The SDK's own caveat rides along into the help text — the filter matches a
task's own assignees, and assignees on nested steps are not considered.
@jeremy
jeremy force-pushed the account-wide-task-filters branch from 4c07e98 to 20506d7 Compare August 4, 2026 02:54
Copilot AI review requested due to automatic review settings August 4, 2026 02:54

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (2)

internal/commands/accountwide.go:202

  • This doc comment starts with rejectEmptyTaskFilterValues, but the function it documents is named validateTaskFilterValues. Every other function comment in this file leads with the function's own name (e.g. validateDueFilter, rejectAssigneeWithUnassigned, accountWideTaskFilters), so this stale name breaks the convention and would confuse anyone grepping for the referenced symbol. Update the comment to begin with the actual function name.
// rejectEmptyTaskFilterValues refuses an explicitly empty --due or --assignee.

internal/commands/accountwide.go:195

  • This comment points readers to a symbol named filterTodosByAssignees, but no such function exists anywhere in the codebase. The project-scoped assignee matching described here actually lives in todoMatchesAnyAssignee (and listTodosInList) in todos.go. The dangling reference sends readers looking for something they will never find; point it at the real symbol instead.
// Project-scoped --assignee is a different animal — see the note on
// filterTodosByAssignees.

@jeremy
jeremy merged commit 32e7cc2 into main Aug 4, 2026
25 checks passed
@jeremy
jeremy deleted the account-wide-task-filters branch August 4, 2026 04:13
jeremy added a commit that referenced this pull request Aug 4, 2026
accountwide.go referred readers to filterTodosByAssignees, which is not
a function in this repository — the project-scoped assignee note lives
in listTodosInList. Point there, and say what the note actually says so
the reference is useful without following it.

The second is mine from #612: I renamed rejectEmptyTaskFilterValues to
validateTaskFilterValues when it took on the --due token check, and left
its doc comment naming the old function.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

commands CLI command implementations docs skills Agent skills tests Tests (unit and e2e)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants