feat(code): add Board view to inbox alongside the existing list#2349
feat(code): add Board view to inbox alongside the existing list#2349fercgomes wants to merge 4 commits into
Conversation
Adds a "View as" toggle to the inbox toolbar (List / Board). Board view groups inbox reports into a horizontal kanban by status (Ready, Needs input, Researching, Queued, Gathering, Failed), each lane sharing the existing `ReportCardContent` so badges, summaries, and selection match the list view. The choice is persisted via `inboxSignalsFilterStore.viewMode`. Infinite-scroll loading is preserved by parking the load-more sentinel at the bottom of the lane with the most reports. Generated-By: PostHog Code Task-Id: b4476476-5cb0-4ab1-8062-555eba7bfbda
Adds a "View as" toggle to the inbox toolbar with a list (default) and board option. The board groups reports into kanban columns by status (Ready, Needs input, Researching, Failed, Queued, Gathering), respecting active status filters. Selecting a card opens the standard detail pane in a right-side panel. The view mode is persisted in the filter store. Generated-By: PostHog Code Task-Id: bb7bd45f-d16b-4cca-a8bd-00856f6c5004
- Each board column now has its own vertical overflow, so it scrolls independently instead of the whole board overflowing the page. - Replaces the nested Radix ScrollArea (which doesn't propagate height through a horizontally-scrolling parent) with plain CSS overflow + a min-h-0 flex chain. - Detail pane in board mode is now 560px by default (was 480px, which squeezed the title in the header) and resizable from its left edge, persisted to its own store so it doesn't fight the list-mode sidebar width. - Infinite-scroll trigger moves to the bottom of the longest column so it actually enters the viewport when scrolling. Generated-By: PostHog Code Task-Id: bb7bd45f-d16b-4cca-a8bd-00856f6c5004
Grouping by `status` is almost useless because nearly all reports sit in `ready` (the pipeline statuses cover the minority of items still being researched), so the board collapses into a single column. Adds a `Group by` menu next to the View toggle (only visible in board mode) with three options — Actionability (default), Priority, Status — and an `inboxBoardGrouping` helper that maps a report to a column id for each option. Actionability shows Actionable / Needs input / Not actionable / In pipeline; Priority shows P0…P4 plus Unprioritized; Status keeps the previous behaviour. Persisted in the existing inbox filter store as `boardGroupBy`. Generated-By: PostHog Code Task-Id: bb7bd45f-d16b-4cca-a8bd-00856f6c5004
|
| const handleBoardDetailResizeMouseDown = useCallback( | ||
| (e: React.MouseEvent) => { | ||
| e.preventDefault(); | ||
| setBoardDetailIsResizing(true); | ||
| document.body.style.cursor = "col-resize"; | ||
| document.body.style.userSelect = "none"; | ||
| }, | ||
| [setBoardDetailIsResizing], | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| const handleMouseMove = (e: MouseEvent) => { | ||
| if (!boardDetailIsResizing || !boardContainerRef.current) return; | ||
| const rect = boardContainerRef.current.getBoundingClientRect(); | ||
| const containerWidth = rect.width; | ||
| const maxWidth = Math.max(420, containerWidth * 0.7); | ||
| const newWidth = Math.max( | ||
| 420, | ||
| Math.min(maxWidth, rect.right - e.clientX), | ||
| ); | ||
| setBoardDetailWidth(newWidth); | ||
| }; | ||
| const handleMouseUp = () => { | ||
| if (boardDetailIsResizing) { | ||
| setBoardDetailIsResizing(false); | ||
| document.body.style.cursor = ""; | ||
| document.body.style.userSelect = ""; | ||
| } | ||
| }; | ||
| document.addEventListener("mousemove", handleMouseMove); | ||
| document.addEventListener("mouseup", handleMouseUp); | ||
| return () => { | ||
| document.removeEventListener("mousemove", handleMouseMove); | ||
| document.removeEventListener("mouseup", handleMouseUp); | ||
| }; | ||
| }, [boardDetailIsResizing, setBoardDetailWidth, setBoardDetailIsResizing]); |
There was a problem hiding this comment.
Board detail pane resize duplicates the existing sidebar resize logic
The board detail pane's handleBoardDetailResizeMouseDown + its useEffect for mousemove/mouseup are structurally identical to the existing sidebar resize that handles handleResizeMouseDown. Both set body.style.cursor/userSelect on drag start, compute width from the same pattern, and clean up on mouseup. This is an OnceAndOnlyOnce violation — consider extracting a shared useResizeHandle(isResizing, setIsResizing, setWidth, containerRef, options) hook that both handles can reuse.
| const handleBoardDetailResizeMouseDown = useCallback( | |
| (e: React.MouseEvent) => { | |
| e.preventDefault(); | |
| setBoardDetailIsResizing(true); | |
| document.body.style.cursor = "col-resize"; | |
| document.body.style.userSelect = "none"; | |
| }, | |
| [setBoardDetailIsResizing], | |
| ); | |
| useEffect(() => { | |
| const handleMouseMove = (e: MouseEvent) => { | |
| if (!boardDetailIsResizing || !boardContainerRef.current) return; | |
| const rect = boardContainerRef.current.getBoundingClientRect(); | |
| const containerWidth = rect.width; | |
| const maxWidth = Math.max(420, containerWidth * 0.7); | |
| const newWidth = Math.max( | |
| 420, | |
| Math.min(maxWidth, rect.right - e.clientX), | |
| ); | |
| setBoardDetailWidth(newWidth); | |
| }; | |
| const handleMouseUp = () => { | |
| if (boardDetailIsResizing) { | |
| setBoardDetailIsResizing(false); | |
| document.body.style.cursor = ""; | |
| document.body.style.userSelect = ""; | |
| } | |
| }; | |
| document.addEventListener("mousemove", handleMouseMove); | |
| document.addEventListener("mouseup", handleMouseUp); | |
| return () => { | |
| document.removeEventListener("mousemove", handleMouseMove); | |
| document.removeEventListener("mouseup", handleMouseUp); | |
| }; | |
| }, [boardDetailIsResizing, setBoardDetailWidth, setBoardDetailIsResizing]); | |
| // TODO: extract a shared useResizeHandle hook to cover both this and the | |
| // sidebar resize below — both use identical mousemove/mouseup logic. | |
| const handleBoardDetailResizeMouseDown = useCallback( | |
| (e: React.MouseEvent) => { | |
| e.preventDefault(); | |
| setBoardDetailIsResizing(true); | |
| document.body.style.cursor = "col-resize"; | |
| document.body.style.userSelect = "none"; | |
| }, | |
| [setBoardDetailIsResizing], | |
| ); | |
| useEffect(() => { | |
| const handleMouseMove = (e: MouseEvent) => { | |
| if (!boardDetailIsResizing || !boardContainerRef.current) return; | |
| const rect = boardContainerRef.current.getBoundingClientRect(); | |
| const containerWidth = rect.width; | |
| const maxWidth = Math.max(420, containerWidth * 0.7); | |
| const newWidth = Math.max( | |
| 420, | |
| Math.min(maxWidth, rect.right - e.clientX), | |
| ); | |
| setBoardDetailWidth(newWidth); | |
| }; | |
| const handleMouseUp = () => { | |
| if (boardDetailIsResizing) { | |
| setBoardDetailIsResizing(false); | |
| document.body.style.cursor = ""; | |
| document.body.style.userSelect = ""; | |
| } | |
| }; | |
| document.addEventListener("mousemove", handleMouseMove); | |
| document.addEventListener("mouseup", handleMouseUp); | |
| return () => { | |
| document.removeEventListener("mousemove", handleMouseMove); | |
| document.removeEventListener("mouseup", handleMouseUp); | |
| }; | |
| }, [boardDetailIsResizing, setBoardDetailWidth, setBoardDetailIsResizing]); |
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/renderer/features/inbox/components/InboxSignalsTab.tsx
Line: 463-498
Comment:
**Board detail pane resize duplicates the existing sidebar resize logic**
The board detail pane's `handleBoardDetailResizeMouseDown` + its `useEffect` for `mousemove`/`mouseup` are structurally identical to the existing sidebar resize that handles `handleResizeMouseDown`. Both set `body.style.cursor/userSelect` on drag start, compute width from the same pattern, and clean up on mouseup. This is an OnceAndOnlyOnce violation — consider extracting a shared `useResizeHandle(isResizing, setIsResizing, setWidth, containerRef, options)` hook that both handles can reuse.
```suggestion
// TODO: extract a shared useResizeHandle hook to cover both this and the
// sidebar resize below — both use identical mousemove/mouseup logic.
const handleBoardDetailResizeMouseDown = useCallback(
(e: React.MouseEvent) => {
e.preventDefault();
setBoardDetailIsResizing(true);
document.body.style.cursor = "col-resize";
document.body.style.userSelect = "none";
},
[setBoardDetailIsResizing],
);
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!boardDetailIsResizing || !boardContainerRef.current) return;
const rect = boardContainerRef.current.getBoundingClientRect();
const containerWidth = rect.width;
const maxWidth = Math.max(420, containerWidth * 0.7);
const newWidth = Math.max(
420,
Math.min(maxWidth, rect.right - e.clientX),
);
setBoardDetailWidth(newWidth);
};
const handleMouseUp = () => {
if (boardDetailIsResizing) {
setBoardDetailIsResizing(false);
document.body.style.cursor = "";
document.body.style.userSelect = "";
}
};
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
return () => {
document.removeEventListener("mousemove", handleMouseMove);
document.removeEventListener("mouseup", handleMouseUp);
};
}, [boardDetailIsResizing, setBoardDetailWidth, setBoardDetailIsResizing]);
```
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!
Summary
ReportBoardPanerenders horizontal lanes forready,pending_input,in_progress,candidate,potential, andfailed, reusing the existingReportCardContent, click/cmd/shift selection, multi-select checkboxes, and motion fade-in animations from the list rows.inboxSignalsFilterStore.viewMode("list" | "board") so it survives reloads. Infinite-scroll is preserved by anchoring the load-more sentinel to the tallest lane.Why
The inbox has been getting noisy with many items at once. Switching to a status-grouped board makes it much easier to scan what's actually actionable (Ready, Needs input) vs. what is still being processed.
Notes
Test plan