Skip to content

feat(sidebar): add "By status" organize mode#2306

Open
fercgomes wants to merge 4 commits into
mainfrom
posthog-code/sidebar-group-by-status
Open

feat(sidebar): add "By status" organize mode#2306
fercgomes wants to merge 4 commits into
mainfrom
posthog-code/sidebar-group-by-status

Conversation

@fercgomes
Copy link
Copy Markdown
Contributor

@fercgomes fercgomes commented May 22, 2026

Summary

Adds a third By status option to the sidebar's organize menu (alongside By project and Chronological list). Tasks are bucketed into kanban-like collapsible sections by their lifecycle state — narrow enough to fit in the sidebar, but conceptually a vertical kanban.

Buckets in fixed precedence + display order:

  1. Needs youneedsPermission
  2. ActiveisGenerating OR taskRunStatus ∈ {queued, in_progress}
  3. DonetaskRunStatus === "completed"
  4. FailedtaskRunStatus ∈ {failed, cancelled}
  5. Idle — everything else (no run, not_started, etc.)
Screenshot 2026-05-22 at 16 07 06

A task lands in the first bucket whose predicate matches, so e.g. an in-progress run that needs permission shows under "Needs you", not "Active".

Empty buckets are hidden. Each section reuses the existing collapsedSections persistence in sidebarStore, so collapse state survives reloads.

Test plan

  • pnpm --filter code typecheck clean
  • New unit tests in groupByStatus.test.ts cover bucket assignment, precedence (Needs-you > Active), display ordering, empty input, and intra-bucket order — all 5 pass
  • All existing sidebar tests still pass (36/36)
  • Open the funnel menu, pick By status — verify sections render in the fixed order with status-colored icons
  • Confirm sections collapse/expand independently and state persists after reload
  • Switch between By project / By status / Chronological and back — verify no layout glitches
  • Verify pinned tasks still appear in the top "Pinned" section across all modes
  • With no tasks, the empty-state hedgehog still renders

fercgomes added 2 commits May 22, 2026 15:50
Adds a third organize option to the sidebar filter funnel that buckets
tasks into kanban-like collapsible sections by their lifecycle state.

Buckets in fixed precedence + display order:
- Needs you (needsPermission)
- Active (isGenerating or queued/in_progress)
- Done (completed)
- Failed (failed or cancelled)
- Idle (everything else)

Empty buckets are hidden. Each section reuses the existing
collapsedSections persistence so collapse state survives reloads.

Generated-By: PostHog Code
Task-Id: d4c57b31-d26d-4679-8a3b-ac6c047b8403
Auto-formatted by `biome check --write` to satisfy the Code Quality CI
check: reflowed the StatusGroup import on one line and re-ordered the
new `groupByStatus` import in useSidebarData to match Biome's organize-
imports rule.

Generated-By: PostHog Code
Task-Id: d4c57b31-d26d-4679-8a3b-ac6c047b8403
@fercgomes fercgomes marked this pull request as ready for review May 22, 2026 19:07
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 22, 2026

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/code/src/renderer/features/sidebar/utils/groupByStatus.test.ts:16-55
**Tests not parameterised**

The team rule is "we always prefer parameterised tests." The bucket-assignment test and the order-preservation test verify one case at a time inside a single `it` block. When a single bucket predicate regresses the failure message points at one giant assertion block rather than at the exact `(input → expected bucket)` pair that broke. Using `it.each` here would also make the "preserves input order" test cover all buckets rather than only `completed`.

For example, bucket assignment could be expressed as `it.each([[{ taskRunStatus: "in_progress" }, STATUS_GROUP_IDS.active], ...])("routes task to correct bucket", (props, expected) => { ... })`.

### Issue 2 of 2
apps/code/src/renderer/features/sidebar/components/TaskListView.tsx:410-429
**TaskRow JSX duplicated across render branches**

The `TaskRow` block here (11 props, same shape) is byte-for-byte identical to the one in the `by-project` branch (lines 479–498) and very close to the chronological and pinned branches. Per the OnceAndOnlyOnce simplicity rule, extracting a small helper — e.g. `renderTaskRow(task, { depth, timestampKey, activeTaskId, ... })` — would let all four branches share one call site and reduce the surface area for future prop additions to miss a branch.

Reviews (1): Last reviewed commit: "style(sidebar): apply biome formatting" | Re-trigger Greptile

Comment on lines +16 to +55
describe("groupByStatus", () => {
it("buckets tasks by their status into the expected groups", () => {
const tasks: TestTask[] = [
task("idle-1"),
task("idle-2", { taskRunStatus: "not_started" }),
task("active-1", { isGenerating: true }),
task("active-2", { taskRunStatus: "in_progress" }),
task("active-3", { taskRunStatus: "queued" }),
task("done-1", { taskRunStatus: "completed" }),
task("failed-1", { taskRunStatus: "failed" }),
task("failed-2", { taskRunStatus: "cancelled" }),
task("needs-1", { needsPermission: true }),
];

const groups = groupByStatus(tasks);
const byId = new Map(groups.map((g) => [g.id, g] as const));

expect(byId.get(STATUS_GROUP_IDS.needsYou)?.tasks.map((t) => t.id)).toEqual(
["needs-1"],
);
expect(byId.get(STATUS_GROUP_IDS.active)?.tasks.map((t) => t.id)).toEqual([
"active-1",
"active-2",
"active-3",
]);
expect(byId.get(STATUS_GROUP_IDS.done)?.tasks.map((t) => t.id)).toEqual([
"done-1",
]);
expect(byId.get(STATUS_GROUP_IDS.failed)?.tasks.map((t) => t.id)).toEqual([
"failed-1",
"failed-2",
]);
expect(byId.get(STATUS_GROUP_IDS.idle)?.tasks.map((t) => t.id)).toEqual([
"idle-1",
"idle-2",
]);
});

it("prefers 'Needs you' over 'Active' when both predicates match", () => {
const tasks: TestTask[] = [
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Tests not parameterised

The team rule is "we always prefer parameterised tests." The bucket-assignment test and the order-preservation test verify one case at a time inside a single it block. When a single bucket predicate regresses the failure message points at one giant assertion block rather than at the exact (input → expected bucket) pair that broke. Using it.each here would also make the "preserves input order" test cover all buckets rather than only completed.

For example, bucket assignment could be expressed as it.each([[{ taskRunStatus: "in_progress" }, STATUS_GROUP_IDS.active], ...])("routes task to correct bucket", (props, expected) => { ... }).

Context Used: Do not attempt to comment on incorrect alphabetica... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/sidebar/utils/groupByStatus.test.ts
Line: 16-55

Comment:
**Tests not parameterised**

The team rule is "we always prefer parameterised tests." The bucket-assignment test and the order-preservation test verify one case at a time inside a single `it` block. When a single bucket predicate regresses the failure message points at one giant assertion block rather than at the exact `(input → expected bucket)` pair that broke. Using `it.each` here would also make the "preserves input order" test cover all buckets rather than only `completed`.

For example, bucket assignment could be expressed as `it.each([[{ taskRunStatus: "in_progress" }, STATUS_GROUP_IDS.active], ...])("routes task to correct bucket", (props, expected) => { ... })`.

**Context Used:** Do not attempt to comment on incorrect alphabetica... ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment on lines +410 to +429
{group.tasks.map((task) => (
<TaskRow
key={task.id}
task={task}
isActive={activeTaskId === task.id}
isEditing={editingTaskId === task.id}
onClick={() => onTaskClick(task.id)}
onDoubleClick={() => onTaskDoubleClick(task.id)}
onContextMenu={(e, isPinned) =>
onTaskContextMenu(task.id, e, isPinned)
}
onArchive={() => onTaskArchive(task.id)}
onTogglePin={() => onTaskTogglePin(task.id)}
onEditSubmit={(newTitle) =>
onTaskEditSubmit(task.id, newTitle)
}
onEditCancel={onTaskEditCancel}
timestamp={task[timestampKey]}
depth={1}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 TaskRow JSX duplicated across render branches

The TaskRow block here (11 props, same shape) is byte-for-byte identical to the one in the by-project branch (lines 479–498) and very close to the chronological and pinned branches. Per the OnceAndOnlyOnce simplicity rule, extracting a small helper — e.g. renderTaskRow(task, { depth, timestampKey, activeTaskId, ... }) — would let all four branches share one call site and reduce the surface area for future prop additions to miss a branch.

Context Used: Do not attempt to comment on incorrect alphabetica... (source)

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/sidebar/components/TaskListView.tsx
Line: 410-429

Comment:
**TaskRow JSX duplicated across render branches**

The `TaskRow` block here (11 props, same shape) is byte-for-byte identical to the one in the `by-project` branch (lines 479–498) and very close to the chronological and pinned branches. Per the OnceAndOnlyOnce simplicity rule, extracting a small helper — e.g. `renderTaskRow(task, { depth, timestampKey, activeTaskId, ... })` — would let all four branches share one call site and reduce the surface area for future prop additions to miss a branch.

**Context Used:** Do not attempt to comment on incorrect alphabetica... ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

fercgomes added 2 commits May 22, 2026 16:25
…ests

After merging main (multi-select feature), TaskRow now requires
`isSelected` / `hideHoverActions` and a new `onClick(taskId, e)`
signature. The new by-status render branch wasn't passing those,
breaking typecheck on CI.

Rather than fix the one branch and leave four near-identical copies of
the TaskRow block, extract a local `renderTaskRow(task, { depth? })`
helper inside `TaskListView` so the four call sites (pinned, by-status,
by-project, chronological) share a single definition. This also
addresses the Greptile review comment about TaskRow JSX being
duplicated across render branches.

Also parametrise the groupByStatus tests with `it.each` per the second
Greptile comment — bucket assignment now covers all five buckets and
nine input shapes, input-order preservation covers all five buckets.

Generated-By: PostHog Code
Task-Id: d4c57b31-d26d-4679-8a3b-ac6c047b8403
The previous commit's `renderTaskRow` extraction modified the three
existing TaskRow call sites (pinned / by-project / chronological) in
the same line ranges that main's multi-select PR also modified. That
created an unavoidable 3-way text conflict against main and blocked
the PR-level `pull_request` CI workflows from being triggered.

Revert the helper extraction so those three call sites match main's
TaskListView.tsx byte-for-byte, and inline the same TaskRow JSX in
the new by-status branch as well. The branch now diverges from main
only in the additive regions (the by-status render branch and radio
item), which 3-way-merges cleanly.

This walks back Greptile's "TaskRow JSX duplicated across render
branches" suggestion — the refactor is a sound style improvement but
can't ship together with multi-select in the same merge window without
a force-push to rewrite history, which isn't an option here.

Generated-By: PostHog Code
Task-Id: d4c57b31-d26d-4679-8a3b-ac6c047b8403
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.

1 participant