mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-20 20:40:38 +09:00
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
This commit is contained in:
parent
7e1a27c8ec
commit
afb73ba553
7 changed files with 904 additions and 32 deletions
250
doc/plans/2026-05-05-scaled-kanban-board.md
Normal file
250
doc/plans/2026-05-05-scaled-kanban-board.md
Normal file
|
|
@ -0,0 +1,250 @@
|
|||
# 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:
|
||||
|
||||
```tsx
|
||||
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:
|
||||
|
||||
```tsx
|
||||
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:
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```bash
|
||||
pnpm exec vitest run ui/src/components/KanbanBoard.test.tsx
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```ts
|
||||
compactCards?: boolean;
|
||||
collapsedStatuses?: string[];
|
||||
initialVisibleCount?: number;
|
||||
revealIncrement?: number;
|
||||
```
|
||||
|
||||
Add a test that stores board mode in localStorage, renders more than 100 issues, and expects:
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```bash
|
||||
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`:
|
||||
|
||||
```ts
|
||||
boardCardDensity: "auto" | "compact" | "comfortable";
|
||||
boardColdLaneMode: "auto" | "collapsed" | "expanded";
|
||||
boardColumnPageSize: 10 | 25 | 50;
|
||||
```
|
||||
|
||||
Default the density modes to `"auto"` and page size to `10`. Derive:
|
||||
|
||||
```ts
|
||||
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:
|
||||
|
||||
```tsx
|
||||
<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:
|
||||
|
||||
```tsx
|
||||
<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:
|
||||
|
||||
```bash
|
||||
pnpm exec vitest run ui/src/components/IssuesList.test.tsx ui/src/components/KanbanBoard.test.tsx
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
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**
|
||||
|
||||
```bash
|
||||
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**
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
```
|
||||
|
||||
Expected: pass.
|
||||
|
||||
- [ ] **Step 3: Check worktree**
|
||||
|
||||
```bash
|
||||
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`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue