Files
paperclip/doc/plans/2026-05-05-scaled-kanban-board.md
Emad Ibrahim afb73ba553 Scale issue kanban board for high-volume columns (#5309)
## Thinking Path

> - Paperclip is a control plane for autonomous AI-agent companies, and
the board UI needs to keep operator visibility clear as company work
scales.
> - The involved subsystem is the Issues page board mode, specifically
the Kanban rendering path for issue status columns.
> - The current board keeps the classic Kanban model, but high-volume
columns can become tall, slow, and hard to scan when hundreds of issues
are loaded.
> - We explored alternatives and chose the conservative Scaled Kanban
direction: preserve status lanes and drag/drop, but bound visible cards
and collapse low-signal lanes.
> - This pull request adds UI-only density controls and high-volume
defaults rather than introducing schema or API changes.
> - The benefit is a board that remains usable with large issue
inventories while keeping active workflow lanes visible.

## What Changed

- Added scaled Kanban behavior with compact cards, collapsed cold-lane
rails, per-column visible-card limits, and per-column "show more" reveal
controls.
- Added persisted board density preferences to the Issues page view
state, scoped through the existing company-specific localStorage path.
- Added board toolbar controls for compact cards, collapsed cold lanes,
cards-per-column page size (`10`, `25`, `50`), and density reset.
- Added a design spec and implementation plan under `doc/plans/`.
- Added focused Vitest coverage for `KanbanBoard` and `IssuesList`
high-volume board behavior.

## Verification

- `pnpm exec vitest run ui/src/components/IssuesList.test.tsx
ui/src/components/KanbanBoard.test.tsx` — pass, 35 tests.
- `pnpm -r typecheck` — pass.
- `pnpm build` — pass before the upstream merge; not rerun after
docs/assets cleanup.
- `curl -fsS http://127.0.0.1:3100/api/health` — pass against restarted
local dev server after applying pending migration
`0078_white_darwin.sql`.
- `pnpm test:run` — previously failed in unrelated Cursor remote-sandbox
server tests:
- `server/src/__tests__/cursor-local-adapter-environment.test.ts`:
expected probe status `pass`, received `fail`.
- `server/src/__tests__/cursor-local-execute.test.ts`: two remote
sandbox execution cases exited `127` instead of `0`.

Local dev server for manual UI inspection: `http://127.0.0.1:3100`.

Screenshots were captured for review and attached in the PR thread
rather than committed to source.

## Risks

- Low schema/API risk: this is UI-only and uses the existing issue data
path.
- Board users may need to notice the new density controls if they want
to override high-volume defaults.
- Collapsed cold lanes remain valid drop targets, so status moves can
happen without expanding the destination lane.
- Very large remote columns can still hit the existing 200-item
per-column query cap; this PR improves rendering, not server-side board
pagination.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- OpenAI Codex coding agent based on GPT-5, with repository tool use,
shell execution, local test/build execution, and inline implementation
planning. No subagents were used.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] If this change affects the UI, I have included before/after
screenshots
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [x] I will address all Greptile and reviewer comments before
requesting merge
2026-05-15 10:53:09 -05:00

8.0 KiB

Scaled Kanban Board Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Make the Issues Kanban board usable with hundreds of issues by adding compact high-volume rendering, collapsed cold lanes, and per-column reveal controls.

Architecture: Keep the change UI-only. IssuesList owns persisted board density preferences in existing company-scoped view state, while KanbanBoard owns lane rendering, card density, collapsed rails, and per-column "show more" state.

Tech Stack: React 19, TypeScript, Vite, Vitest/jsdom, @dnd-kit/core, @dnd-kit/sortable, Tailwind utility classes.


File Structure

  • Modify ui/src/components/IssuesList.tsx: extend IssueViewState, derive high-volume board preferences, add toolbar controls, pass props into KanbanBoard.
  • Modify ui/src/components/KanbanBoard.tsx: add compact cards, collapsed rail lanes, visible-card limits, and per-column reveal behavior.
  • Create ui/src/components/KanbanBoard.test.tsx: focused tests for high-volume behavior and drag/drop update callback.
  • Modify ui/src/components/IssuesList.test.tsx: update the mocked KanbanBoard expectations for new props.
  • Keep doc/plans/2026-05-05-scaled-kanban-board-design.md as the design source of truth.

Task 1: Add Kanban Board Scaling Mechanics

Files:

  • Modify: ui/src/components/KanbanBoard.tsx

  • Create: ui/src/components/KanbanBoard.test.tsx

  • Step 1: Write focused tests

Create ui/src/components/KanbanBoard.test.tsx with tests that render 60 todo issues and assert:

renderBoard({ issues: createIssues(60, "todo"), compactCards: true, initialVisibleCount: 10, revealIncrement: 10 });
expect(container.textContent).toContain("Showing 10 of 60");
expect(container.textContent).toContain("Show 10 more");

Also test collapsed rails:

renderBoard({ issues: createIssues(3, "done"), collapsedStatuses: ["done"] });
expect(container.textContent).toContain("Done");
expect(container.textContent).toContain("3");
expect(container.textContent).not.toContain("Issue 1");
  • Step 2: Run tests to verify failure

Run:

pnpm exec vitest run ui/src/components/KanbanBoard.test.tsx

Expected: fail because KanbanBoard.test.tsx is new and the props/behavior do not exist.

  • Step 3: Implement minimal board behavior

In KanbanBoard.tsx, add exported constants:

export const KANBAN_BOARD_HIGH_VOLUME_THRESHOLD = 100;
export const KANBAN_COLUMN_PAGE_SIZE_OPTIONS = [10, 25, 50] as const;
export const KANBAN_COLUMN_DEFAULT_PAGE_SIZE = 10;
export const KANBAN_COLD_STATUSES = ["backlog", "done", "cancelled"] as const;

Extend props:

compactCards?: boolean;
collapsedStatuses?: string[];
initialVisibleCount?: number;
revealIncrement?: number;

Add per-status visible-count state keyed by status. Expanded columns render issues.slice(0, visibleCount) and show a button when hidden issues remain. Collapsed columns render a narrow droppable rail with status icon, label, and count, but no cards.

Reset per-status visible-count state when initialVisibleCount or revealIncrement changes so choosing a smaller cards-per-column preset does not leave a column expanded past the newly selected page size.

  • Step 4: Preserve drag/drop

Keep DndContext, SortableContext, and handleDragEnd status detection. Because collapsed rails use useDroppable({ id: status }), dropping a visible card onto a rail continues to resolve targetStatus through the existing status-id branch.

  • Step 5: Run focused test

Run:

pnpm exec vitest run ui/src/components/KanbanBoard.test.tsx

Expected: pass.

  • Step 6: Commit
git add ui/src/components/KanbanBoard.tsx ui/src/components/KanbanBoard.test.tsx
git commit -m "Scale kanban board columns"

Task 2: Wire Board Density State Into IssuesList

Files:

  • Modify: ui/src/components/IssuesList.tsx

  • Modify: ui/src/components/IssuesList.test.tsx

  • Step 1: Write/update tests

In IssuesList.test.tsx, update the KanbanBoard mock to capture:

compactCards?: boolean;
collapsedStatuses?: string[];
initialVisibleCount?: number;
revealIncrement?: number;

Add a test that stores board mode in localStorage, renders more than 100 issues, and expects:

expect(mockKanbanBoard).toHaveBeenLastCalledWith(expect.objectContaining({
  compactCards: true,
  collapsedStatuses: expect.arrayContaining(["backlog", "done", "cancelled"]),
  initialVisibleCount: 10,
  revealIncrement: 10,
}));
  • Step 2: Run test to verify failure

Run:

pnpm exec vitest run ui/src/components/IssuesList.test.tsx

Expected: fail because IssuesList does not pass the new props yet.

  • Step 3: Add persisted board density preferences

Extend IssueViewState:

boardCardDensity: "auto" | "compact" | "comfortable";
boardColdLaneMode: "auto" | "collapsed" | "expanded";
boardColumnPageSize: 10 | 25 | 50;

Default the density modes to "auto" and page size to 10. Derive:

const boardHighVolume = viewState.viewMode === "board" && filtered.length > KANBAN_BOARD_HIGH_VOLUME_THRESHOLD;
const boardCompactCards = viewState.boardCardDensity === "compact"
  || (viewState.boardCardDensity === "auto" && boardHighVolume);
const boardCollapsedStatuses = viewState.boardColdLaneMode === "collapsed"
  || (viewState.boardColdLaneMode === "auto" && boardHighVolume)
    ? [...KANBAN_COLD_STATUSES]
    : [];
  • Step 4: Add toolbar controls

When viewState.viewMode === "board", add small outline/icon buttons near the existing view controls:

<Button ... title={boardCompactCards ? "Use comfortable cards" : "Use compact cards"}>...</Button>
<Button ... title={boardCollapsedStatuses.length > 0 ? "Expand cold lanes" : "Collapse cold lanes"}>...</Button>
<Button ... title="Cards per column">...</Button>
<Button ... title="Reset board density">...</Button>

Use lucide icons already available or import ChevronsDownUp, PanelTopClose, and RotateCcw.

  • Step 5: Pass board props

Update the KanbanBoard call:

<KanbanBoard
  issues={filtered}
  agents={agents}
  liveIssueIds={liveIssueIds}
  compactCards={boardCompactCards}
  collapsedStatuses={boardCollapsedStatuses}
  initialVisibleCount={viewState.boardColumnPageSize}
  revealIncrement={viewState.boardColumnPageSize}
  onUpdateIssue={onUpdateIssue}
/>
  • Step 6: Run focused tests

Run:

pnpm exec vitest run ui/src/components/IssuesList.test.tsx ui/src/components/KanbanBoard.test.tsx

Expected: pass.

  • Step 7: Commit
git add ui/src/components/IssuesList.tsx ui/src/components/IssuesList.test.tsx
git commit -m "Wire issue board density controls"

Task 3: Verification And PR Prep

Files:

  • Verify existing changes only.

  • Step 1: Run targeted UI tests

pnpm exec vitest run ui/src/components/IssuesList.test.tsx ui/src/components/KanbanBoard.test.tsx

Expected: pass.

  • Step 2: Run broader cheap test path
pnpm test

Expected: pass.

  • Step 3: Check worktree
git status --short

Expected: only intentional changes before committing, or clean after final commit.

  • Step 4: Prepare PR

Read .github/PULL_REQUEST_TEMPLATE.md and use it for the PR body. Include:

  • design spec path
  • scaled Kanban behavior summary
  • test commands and results
  • Model Used section with the current Codex model details available in this session

Self-Review

  • Spec coverage: The plan covers compact high-volume board cards, collapsed cold lanes, cards-per-column presets, per-column reveal controls, persisted board preferences, current API reuse, and focused tests.
  • Placeholder scan: No unresolved markers or unspecified implementation placeholders remain.
  • Type consistency: The plan consistently uses boardCardDensity, boardColdLaneMode, boardColumnPageSize, compactCards, collapsedStatuses, initialVisibleCount, and revealIncrement.