5.6 KiB
Table Sorting Consistency Spec (#620)
Problem
CoreScope has 20+ data tables. Only 2 are sortable (nodes list, channel activity). Those 2 use incompatible implementations — different property names (column/direction vs col/dir), different data attributes (data-sort vs data-sort-col), different function signatures. The remaining 18+ tables, including the packets table (30K+ rows), have zero sorting.
This violates AGENTS.md DRY rules and frustrates users who can see data but can't reorder it.
Solution
One shared TableSort module. Every data table uses it. Same UX everywhere.
Shared Utility Design
Module: public/table-sort.js
IIFE pattern (like channel-colors.js). No dependencies. No build step.
window.TableSort = (function() {
return { init, sort, destroy };
})();
API
TableSort.init(tableEl, {
defaultColumn: 'last_seen', // initial sort column
defaultDirection: 'desc', // 'asc' or 'desc'
storageKey: 'nodes-sort', // localStorage key (optional)
comparators: { // custom comparators for non-string columns
time: (a, b) => ...,
snr: (a, b) => ...,
},
onSort: (column, direction) => {} // callback after sort completes
});
How It Works
- Scans
<th>elements fordata-sort="columnName"attribute - Attaches click handlers — click toggles asc/desc
- On sort: reads
<td data-value="...">(raw sortable value) from each row - Sorts rows in-place via DOM reorder (no innerHTML rebuild — important for 30K rows)
- Updates visual indicator and
aria-sorton active<th>
Visual Indicator
Active column header gets ▲ (ascending) or ▼ (descending) appended as a <span class="sort-arrow">. Inactive columns show no arrow. CSS class .sort-active on the active <th>.
Built-in Comparators
| Type | Detected From | Behavior |
|---|---|---|
numeric |
data-type="number" on <th> |
Number(a) - Number(b), NaN sorts last |
text |
default | localeCompare |
date |
data-type="date" |
Parse as timestamp, numeric compare |
dbm |
data-type="dbm" |
Strip " dBm" suffix, numeric compare |
Custom comparators in options.comparators override built-in types.
Accessibility
aria-sort="ascending","descending", or"none"on every sortable<th>role="columnheader"(already implicit for<th>)cursor: pointerand:hoverstyle on sortable headers- Keyboard: sortable headers are focusable, Enter/Space triggers sort
Performance (Critical for Packets Table)
- Sort via DOM node reorder (
appendChildloop), notinnerHTML. Browser batches reflows. data-valueattributes hold raw values — no parsing during sort.- For 30K rows: expected sort time ~100-200ms (single
Array.sort+ DOM reorder). If >500ms, add a virtual scroll layer in a follow-up — but don't pre-optimize. - No re-render of row content. Sort only changes order.
Milestones
M1: Shared utility + packets table
- Create
public/table-sort.js - Unit tests:
test-table-sort.js(Node.js, jsdom or vm.createContext) - Integrate with packets table (highest impact — 30K rows, currently unsortable)
- Default sort: time descending
- Columns: all current packets columns (Region, Time, Hash, Size, HB, Type, Observer, Path, Rpt, Details)
- Browser validation: sort 30K rows, verify <500ms
M2: Nodes list + node detail tables
- Migrate nodes list from custom sort to
TableSort.init() - Add sorting to neighbor table (side pane + detail page)
- Add sorting to observer stats table (detail page)
- Remove old
sortState/sortArrowcode fromnodes.js
M3: Analytics tables
- Hash collisions tables (node table, sizes table, collision prefixes)
- RF statistics table
- Route frequency, co-appearance, topology tables
- Node health tables (top by packets/SNR/observers, recently active)
- Distance tables (by link type, top 20 longest)
- Per-node analytics: peer contacts
M4: Channels list + observers list + comparison table
- Channel activity table: migrate from custom sort to
TableSort.init() - Remove old
_channelSortStatecode fromanalytics.js - Observers list table
- Comparison table (
compare.js)
M5: Cleanup
- Remove all old sorting code (both implementations)
- Verify no dead CSS/JS from old sort code
- Final consistency audit: every data table uses
TableSort.init()
Out of Scope
packets.jshex breakdown (structural decode, fixed order)audio-lab.jsdebug tables (not user-facing)- Virtual scroll / pagination (separate issue if perf requires it)
Testing
Unit Tests (test-table-sort.js)
- Numeric sort ascending/descending
- Text sort with localeCompare
- Date sort
- dBm sort (strip suffix)
- Custom comparator override
- NaN/null/undefined sort to end
- Toggle direction on repeated click
aria-sortattribute updates- localStorage persistence (read + write)
data-valueattribute used over text content
Integration (per milestone)
- Playwright test: click column header, verify row order changes
- Playwright test: click again, verify direction toggles
- Playwright test: visual indicator present on active column
Performance
- Unit test: sort 30K mock rows in <500ms (assert timing)
- Required per AGENTS.md: perf claims need proof
Migration Path
Existing sort code in nodes.js and analytics.js will be replaced, not wrapped. Both current implementations are <100 lines each — replacing is simpler than adapting. The shared utility subsumes all their functionality.
Old localStorage keys (nodes-sort-*, channel sort state) should be migrated or cleared on first use of the new utility.