Skip to content

List view

Source: atrium/frontend/src/components/ListView.jsx Category: View

List view — a flat table of every task, sortable by any column, optionally grouped (by status, priority, assignee, project). Used when the kanban is too wide or the user wants to scan many tasks at once.

A CSS grid with a fixed header row and one row per task. Columns are id, title, status, type, assignee, priority, tags, created-at. Clicking a header sorts; click again to reverse. Grouping collapses the table into sections with count headers.

The kanban is great for “what’s in flight today”, bad for “have we done anything about rate limiting in the last month”. The list view is for scanning across status boundaries. Same data, different lens.

IDTitleStatusTypeAssignee
feat-auth-001Implement JWT Loginin progressfrontend@Agent-FE
bug-port-009Admin link rejects scheme-less URLsreviewbackend@opus-agent
feat-port-043Make an Open Source Contributiontodo
ui-filters-003Add saved filter presetstodofrontend
opt-perf-004Memoize TaskCarddonefrontend@opus-agent
feat-port-046Light mode togglereviewfrontend@opus-agent
bug-shortids-001Project display name lost on restartreviewbackend@opus-agent
const columns = ['id', 'title', 'status', 'type', 'assignee', 'priority'];
export default function ListView({ tasks }) {
const [sortKey, setSortKey] = useState('priority');
const [sortDir, setSortDir] = useState('asc');
const [groupBy, setGroupBy] = useState('none');
const sorted = useMemo(() => [...tasks].sort(compare(sortKey, sortDir)), [tasks, sortKey, sortDir]);
const grouped = groupBy === 'none' ? { All: sorted } : groupBy_(sorted, groupBy);
return (
<table>
<Header columns={columns} sortKey={sortKey} sortDir={sortDir} onSort={setSortKey} />
{Object.entries(grouped).map(([group, items]) => (
<GroupSection key={group} label={group} count={items.length} items={items} />
))}
</table>
);
}
  • Atrium — toggle between Board / List / Tree from the top-right view switcher. Preferences persist in localStorage.
  • Pattern generalizes to any collection that benefits from both a card view and a table view
  • Virtualization kicks in around ~200 rows. Below that, plain render is fine; above, scroll performance tanks in non-virtualized tables. Atrium uses @tanstack/react-virtual when task count crosses a threshold.
  • Sort stability. JS Array.sort isn’t stable in some older runtimes; modern engines are. If you see tasks shuffling on repeated sorts, convert to a comparator that breaks ties by id.
  • Column widths are a design decision. Fixed-width columns for id, status, type, assignee; flex column for title. Otherwise a long title pushes everything else off-screen.
  • Grouping adds a second axis. When combined with sorting, the user’s mental model is “sort within groups”. Make that explicit; don’t let grouping override sorting silently.
  • Row density vs scannability. Dense rows fit more tasks per screen; sparse rows are more readable. Atrium ships one density and lets users tweak via a settings toggle.
  • Sticky header matters. Scrolling long lists without a sticky header is unusable. position: sticky on the header row, not fixed — sticky respects the table’s scroll container.