Repository navigation
fix(analytics): sort the Hash Stats adopters table by the clicked column, with direction (#226) - #230
Conversation
…on and deep link (#226) Red on master: the header map misses Role, so each header sorts by the cell to its left (Role does nothing, Last Seen compares "x ago" text); the sort is ascending only, has no indicator, is lost at the next filter click and is not in the URL. - test-hash-stats-sort-226.js: each column's own cells in order, both directions; Last Seen by timestamp with a missing one last; aria-sort and arrow; header and filter clicks through the card's real handler; mbsort=/mbdir= written next to mbf=. - test-analytics-subtab-deeplinks-205.js: mbsort=/mbdir= deep links on the whole page, hostile values give the server order and a canonical URL, URL only, dropped on a tab switch. - test-issue-226-hash-stats-sort-e2e.js (added to the Playwright step): every header and both directions on the fixture, filter click, reload, deep link, hostile value, tab switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…umn, with direction (#226) The header click sorted the rendered rows by a column map that missed Role, so every header read the cell to its left, and Last Seen compared the "5m ago" text. It sorted ascending only, showed nothing, and the next filter click re-rendered the table in the server's order. The sort is now state of the card ({ col, dir }) and is applied to the row data before the table is rendered, by the same buildTableContent the filter click uses, so a filter click keeps it. Each column sorts by its own value: Node and Role as lower-case text, Status by weight, Hash Size and Adverts as numbers, Last Seen by timestamp (a missing one last in both directions); ties keep the server's order. Clicking the sorted header flips the direction, another header starts ascending; the sorted header gets aria-sort, sort-active and the arrow the channel table uses. The sort is deep-linked as mbsort= and mbdir= next to mbf=, URL only, through the #205/#208 view-param helpers: values are only compared with the fixed column keys, an unknown one is the default (the server's order), and a direction without a column is dropped. Both keys are dropped on a tab switch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rapport — CS-Macmini PR#230 #226 — head 523b3fbStatus: Draft PR open. All local unit and E2E tests are green, the new tests are red on master, 8 of 8 mutants are killed, and the XSS diff gate is clean. CI was still running when I posted (run Evidence: [T] = ran it, [A] = read in source or diff, [K] = known from earlier work or another report, not re-verified here. Base and commits:
Investigation first [A]
Acceptance criteria
All mutants were applied one at a time to Tests run [T]
ScreenshotA screenshot of CIRun Remaining
|
Review — CS-Minimax PR#230 hash-stats-sort — head 523b3fbDom: APPROVE med nits An independent, read-only review. I made no changes to the PR or the branch. Evidence: [T] = I ran it, [A] = I read it in the source or diff, [K] = known from the author's report or earlier work, not re-verified here. Trees:
Findings
There are no blocking findings. 1. Correct column — OK
2. Direction — OK
3. State — OK
4. Deep link (
|
| Test | Result |
|---|---|
sh test-all.sh |
216 passed, 0 failed (216 files), exit 0 |
node test-frontend-helpers.js |
707 passed, 0 failed |
node test-hash-stats-sort-226.js |
21 passed |
node test-analytics-subtab-deeplinks-205.js |
121 passed |
E2E setup:
- A local Go server built from the merged tree, on
test-fixtures/e2e-fixture.db. - The fixture was prepared as in CI:
freshen-fixture.sh, the Packets page collapse button in the left column of the table opens the dialog. Kpa-clawbot/CoreScope#1486/"Group Data" message type missing from packet view window's "message type" filter Kpa-clawbot/CoreScope#1791 seed fromdeploy.yml,corescope-migrate, thenseed-2073-route-adverts.sqlandseed-199-inactive-observer.sql. - Served with
-public public, not instrumented. - The server was stopped by the pid of the port listener, not
$!.
| E2E | Result |
|---|---|
test-issue-226-hash-stats-sort-e2e.js |
15 passed |
test-issue-205-analytics-subtab-deeplinks-e2e.js |
15 passed |
test-issue-1306-collisions-terminology-e2e.js |
23 passed |
Red on master: I swapped in origin/master's public/analytics.js on the merged tree and restored it afterwards (checked with cmp):
test-hash-stats-sort-226.js: 18 of 21 fail.test-analytics-subtab-deeplinks-205.js: 16 of 121 fail.test-issue-226-hash-stats-sort-e2e.js: 12 of 15 fail.
These match the author's numbers.
Mutants (my own, applied one at a time to public/analytics.js, then restored) [T]
| Mutant | unit 226 | deeplink 205 | E2E 226 | E2E 205 | Killed |
|---|---|---|---|---|---|
M1 column offset +1 (each header's data-sort = the next column's key) |
15 fail | pass | 9 fail | pass | yes |
M2 Last Seen sorted as timeAgo text |
4 fail | 1 fail | 4 fail | pass | yes |
M3 direction ignored after a filter click (re-render with dir: 'asc') |
1 fail | pass | 1 fail | pass | yes |
M4 mbdir left out of TAB_URL_PARAMS (not dropped on a tab switch) |
pass | 1 fail | 1 fail | pass | yes |
| M5 no reset of a direction without a column | pass | 8 fail | 1 fail | pass | yes |
| M6 Status weights rotated (unknown first) | 3 fail | pass | pass | pass | yes |
M7 aria-sort inverted |
2 fail | pass | 9 fail | pass | yes |
| M8 missing Last Seen first when descending | 2 fail | pass | pass | pass | yes |
| M9 a new column inherits the previous direction | 1 fail | pass | pass | pass | yes |
| M10 ties in reverse server order (unstable) | 4 fail | pass | pass | pass | yes |
test-frontend-helpers.js stayed green under every mutant, as expected, since it does not cover this card. After the run the file was byte-identical to the merged tree. M6, M8, M9 and M10 are killed only by the unit test, because the fixture's Status, Hash Size and Adverts values are almost uniform. The author pointed this out, and the unit fixture covers those cases with distinct values.
Browser [T]
On the local server and fixture, in the built-in browser:
- Deep link:
#/analytics?tab=hashsizes&mbsort=lastSeen&mbdir=descshows LAST SEEN highlighted in the link colour with ↓, the other headers with ⇅, and rows 7m, 10m, 26m, 27m … ago. - Filter: "All (21)" is active.
- Re-renders: a header click, a filter click,
theme-refresh, a time-window change and a tab switch behave as described in sections 3 and 4. - Screenshot: taken locally. I can't attach an image to a comment from the CLI, so it is not included here.
CI
Run 37204718658 on 523b3fbd:
- Go Build & Test: success.
- Release Artifacts: skipped, as expected on a PR.
- Playwright E2E Tests: success. The job log shows
test-issue-226-hash-stats-sort-e2e.js: PASS, along with the analytics: deep-link inner views (Scopes sub-tabs + window, audit other tabs' local view state) #205 and bug(analytics): Prefix Tool says "38 two-byte collisions", Collisions tab says "0" — same word, different math, operators confused Kpa-clawbot/CoreScope#1306 E2Es. - Build & Publish Docker Image: success.
- Deploy Staging and Publish Badges & Summary: skipped.
- Run conclusion: success.
Not verified
- Not run locally: the full Playwright suite (
test-e2e-playwright.jsand the rest of thedeploy.ymlE2E list),test-a11y-axe-1668.jsand instrumented-coverage collection. I ran only the PR's E2E and the analytics E2Es named above. - Browsers: Safari and Firefox were not tested.
Date.parseon the ISO-Z format is standard. - Screen reader: not tested with a real one; F2 is based on reading the markup.
- Large meshes: no measurement with a large adopter list. The complexity argument is in section 6.
- No staging or prod access, per the review scope.
Relates to #226
The Hash Stats multi-byte adopters table sorted by the wrong column and had no sort state. This makes the sort state of the card, applies it to the row data before the table renders, and deep-links it next to
mbf=.What was wrong
colIdx = { name: 0, status: 1, hashSize: 2, packets: 3, lastSeen: 4 }, but the table has six columns (Role was added with the map in45623672). Each header read the cell to its left: Role did nothing, Status read Role, Adverts read Hash Size, Last Seen read Adverts.timeAgotext ("5m ago") as strings even with the right index.Change (
public/analytics.js){ col, dir }. A header click re-renders the table through the samebuildTableContentthe filter click uses, so a filter click keeps the sort and the header shows it.aria-sort,sort-activeand the arrow the channel table already uses (channelSortArrow); no new colours or CSS.mbsort=<name|role|status|hashSize|packets|lastSeen>andmbdir=<asc|desc>, URL only likembf=, through the analytics: deep-link inner views (Scopes sub-tabs + window, audit other tabs' local view state) #205/follow-ups from #203, #204 and #206 reviews (P3: test gaps, inactive-card wording, history defaults, Hash Stats audit) #208 view-param helpers. Values are only compared with the fixed column keys, never put in a selector or markup; an unknown value is the default (the server's order), and a direction without a column is dropped. Both keys are dropped on a tab switch. Small enough to keep: two specs, onerestoreViewParamscall, one_writeViewParamson click.mbf=are unchanged.Performance
The adopters list is small (21 rows on the E2E fixture; the card lists nodes that advertised with multi-byte hashes). A header click sorts that list once, O(n log n), and re-renders only the table inside the card, as a filter click already does. No API call, no work on other tabs.
Tests
test-hash-stats-sort-226.js(new, intest-all.sh): per column and direction, the column's own cells come out in order; Last Seen follows the timestamps; no sort or an unknown column keeps the server order;aria-sortand the arrow on the sorted column only; header and filter clicks through the card's real click handler;mbsort=/mbdir=written next tombf=; hostile values never reach the markup. 18 of 21 red on master.test-analytics-subtab-deeplinks-205.js:mbsort=/mbdir=deep links on the whole page, hostile values, URL only, dropped on a tab switch. 16 of the new tests red on master.test-issue-226-hash-stats-sort-e2e.js(new, added to the Playwright step): every header, both directions, against the fixture's own cells (Last Seen against the API timestamps), filter click, reload, deep link, hostile value, tab switch. 12 of 15 red against master's frontend.Details, mutants and the screenshot are in the report comment.
🤖 Generated with Claude Code