Skip to content

Centralize visible issue filtering and Done retention#222

Merged
joryirving merged 10 commits into
mainfrom
feature/centralize-board-columns
May 26, 2026
Merged

Centralize visible issue filtering and Done retention#222
joryirving merged 10 commits into
mainfrom
feature/centralize-board-columns

Conversation

@itsmiso-ai

Copy link
Copy Markdown
Contributor

Summary

This PR centralizes visible issue filtering across Board, Projects, and the Issues API route using a shared helper function. This ensures consistent Done retention behavior across all views.

Changes

New shared helpers in src/lib/issue-filters.ts

  • buildVisibleIssueWhere(where, options) — Adds OR-based visibility filtering to a Prisma where clause:
    • Default: open issues + recently closed Done issues (within retention window)
    • With includeClosed=true: all issues, no state filter
  • getDoneRetentionDays() — Reads DISPATCH_DONE_RETENTION_DAYS env var, defaults to 7

Updated pages/routes to use shared helper

  • Board page (src/app/board/page.tsx) — Replaced inline filtering logic with buildVisibleIssueWhere()
  • Projects page (src/app/projects/page.tsx) — Replaced inline filtering logic with buildVisibleIssueWhere()
  • Issues API route (src/app/api/issues/route.ts) — Key fix: was filtering to open-only by default, now applies done retention window (7 days) matching Board and Projects

Tests

  • Added comprehensive tests for buildVisibleIssueWhere() covering:
    • Default behavior (open + recently closed Done)
    • includeClosed=true shows all issues
    • Custom doneRetentionDays option
    • Preservation of existing where clauses
  • Added tests for getDoneRetentionDays() covering:
    • Default value of 7
    • DISPATCH_DONE_RETENTION_DAYS env var support
    • Invalid values clamped to default
  • Created new test file src/app/api/issues/route.test.ts covering:
    • Default filtering behavior (open + recently closed Done)
    • includeClosed=true query param
    • Repository, agent, owner, priority, project, decomposed filters
    • Error handling

Acceptance criteria met

  • Board and Projects share Done retention behavior via shared helper
  • Issues API route now applies the same done retention (was open-only before)
  • Default retention is 7 days (configurable via DISPATCH_DONE_RETENTION_DAYS)
  • All 938 tests pass, lint and typecheck clean

Fixes #196

- Extract MobileNav into src/components/mobile-nav.tsx with React state
- Add theme toggle and version label inside mobile nav panel
- Nav closes automatically when a link is tapped (hashchange + onClick)
- Desktop nav hidden on small screens (hidden sm:flex)
- Header and main content use consistent max-w-screen-2xl container
- Restore version label to desktop header (was accidentally removed)
- Conditionally render mobile nav content only when open to avoid
  duplicate DOM elements (version label, theme toggle)
- Update tests: use fireEvent for checkbox interaction, add new
  test for sm:hidden container, verify nav links on open
- Add src/lib/version-client.ts with getClientVersionLabel() that
  uses only NEXT_PUBLIC_DISPATCH_VERSION (no Node.js fs imports)
- mobile-nav.tsx now uses getClientVersionLabel to avoid bundling
  node:fs into the client bundle (Turbopack chunking error)
- ThemeToggle now renders inside mobile nav footer alongside version label
- Desktop header still has its own ThemeToggle (right-aligned via ml-auto)
- Update test to query desktop toggle via .ml-auto selector instead of
  getByLabelText which would match both instances
- Add BOARD_COLUMNS as shared source of truth in src/types/index.ts
- Derive STATUS_LABELS from BOARD_COLUMNS to prevent drift
- Update KanbanBoard to use BOARD_COLUMNS instead of local COLUMNS
- Update Projects page to use BOARD_COLUMNS with human-readable titles
- Add tests for BOARD_COLUMNS canonical order and STATUS_LABELS derivation
- Update ProjectsPage test to expect human-readable column titles
- Add buildVisibleIssueWhere() shared helper in src/lib/issue-filters.ts
- Add getDoneRetentionDays() with DISPATCH_DONE_RETENTION_DAYS support (default 7)
- Update Board page to use the shared helper instead of inline logic
- Update Projects page to use the shared helper instead of inline logic
- Update Issues API route to use shared helper (was open-only, now applies done retention)
- Add comprehensive tests for buildVisibleIssueWhere and getDoneRetentionDays
- Add tests for GET /api/issues route covering filtering behavior

Fixes #196
@its-miso

its-miso Bot commented May 26, 2026

Copy link
Copy Markdown
Contributor

Automated recommendation: APPROVE

Analysis engine: MiniMax-M2.7@https://litellm.jory.dev/v1 (anthropic)

Summary

This PR centralizes visible issue filtering across Board, Projects, and the Issues API route using a shared helper function (buildVisibleIssueWhere). The implementation addresses issue #196 by ensuring consistent Done retention behavior (7-day default) across all views.

Change-by-Change Findings

src/lib/issue-filters.ts (+34 lines)

  • Added VisibleIssueWhereOptions interface with includeClosed and doneRetentionDays options
  • Added DEFAULT_DONE_RETENTION_DAYS = 7 constant
  • Added getDoneRetentionDays() that reads DISPATCH_DONE_RETENTION_DAYS env var, defaults to 7, clamps invalid values (0, negative, NaN) to default
  • Added buildVisibleIssueWhere(where, options?) that mutates the where clause in-place:
    • When includeClosed=true: returns early, no state filter (shows all issues)
    • Default behavior: sets where.OR with open issues + recently closed Done issues within retention window
  • Implementation correctly handles the OR filter structure with { state: "open" } and { state: "closed", labels: { has: "status/done" }, closedAt: { gte: cutoff } }

src/app/api/issues/route.ts (+2, -5 lines)

  • Key fix: Replaced 5-line inline filtering (if (includeClosed !== "true") { where.state = "open" }) with buildVisibleIssueWhere(where, { includeClosed: includeClosed === "true" })
  • This changes the API route from open-only by default to open + recently closed Done issues with 7-day retention, matching Board/Projects behavior

src/app/board/page.tsx (+3, -24 lines)

  • Removed inline DONE_RETENTION_DAYS constant and getDoneRetentionCutoff() function
  • Replaced 15-line inline OR filter logic with single buildVisibleIssueWhere(where, { includeClosed }) call
  • Updated static string message to use getDoneRetentionDays() for dynamic retention day display

src/app/projects/page.tsx (+5, -20 lines)

  • Removed inline DONE_RETENTION_DAYS constant and getDoneRetentionCutoff() function
  • Replaced 15-line inline OR filter with buildVisibleIssueWhere(where) call
  • Now uses shared helper instead of duplicate inline logic

.env.example (+3 lines)

  • Added documented DISPATCH_DONE_RETENTION_DAYS env var with default value of 7

src/lib/issue-filters.test.ts (+113 lines)

  • Added 6 tests for buildVisibleIssueWhere: default 7-day retention, includeClosed=true behavior, custom doneRetentionDays option, env var respect, preserves existing where clauses
  • Added 3 tests for getDoneRetentionDays: default 7, env var respect, invalid value clamping (NaN, 0, negative)
  • Tests properly reset process.env in beforeEach blocks

src/app/api/issues/route.test.ts (new, +148 lines)

  • 14 tests covering default filtering, includeClosed param, env var respect, all filter params (repo, agent, owner, priority, project, decomposed), ordering, includes, combined filters, response shape, error handling
  • Tests verify the actual Prisma findMany call arguments via mock assertions

Standards Compliance

No agent-specific names — Code uses generic patterns; no hardcoded agent identifiers
TypeScript typesVisibleIssueWhereOptions interface exported, proper typing throughout
Error handling — API route has try/catch with 500 error response (confirmed in route.test.ts)
ValidationgetDoneRetentionDays() validates and clamps invalid env values
Environment variables — Follows repository pattern for optional config with sensible defaults
Tests — Use vitest (repository standard), include error path coverage, reset mocks between tests
No secrets in code — No .env files committed, no token logging

Linked Issue Fit

Issue #196 acceptance criteria comparison:

Criterion Status
Board and Projects share Done retention behavior via shared helper ✅ Both pages now use buildVisibleIssueWhere() from src/lib/issue-filters.ts
Issues API route applies the same done retention ✅ Fixed from open-only to open + 7-day Done retention
Default retention is 7 days DEFAULT_DONE_RETENTION_DAYS = 7, confirmed in tests
includeClosed=true still works ✅ Both helper and route tests verify this
DISPATCH_DONE_RETENTION_DAYS env var support ✅ Implemented in getDoneRetentionDays(), tested

Implementation matches suggested approach from issue: shared helper function buildVisibleIssueWhere({ includeClosed, doneRetentionDays, filters }) with env var support.

Tool Harness Findings

No tool requests were executed. The PR is a refactoring change (no image updates, no version changes) that can be fully validated through code review of the diff and test coverage provided.

Unknowns / Needs Verification

  • Test count (938): PR claims "All 938 tests pass" — this was not independently verified as tool harness ran in plan-only mode with no requests executed. The test suite size (+261 test lines across two files) is substantial and covers the new functionality.

  • CI validation: Standard GitHub Actions workflow should run lint, typecheck, tests, and build automatically. The PR body states these are clean but this reviewer cannot confirm without execution.

@joryirving joryirving merged commit 32e5377 into main May 26, 2026
3 checks passed
@joryirving joryirving deleted the feature/centralize-board-columns branch May 26, 2026 15:03
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.

Centralize visible issue filtering and Done retention

2 participants