MotirBuilding in public
MOTIR · moooon
onMotir
You’re viewing a public project. Anyone can view it — no account needed. Sign in to submit, upvote, or comment on requests.View-only — you can’t edit work items
MOTIR-125

2.5.14 TreeTable — lazy-expand + sortable headers (wire 2.5.13)

Done
Description

Estimate: 26m · Depends on: 2.5.2, 2.5.13, 2.5.11

Rework the Tree rendering to lazy-load + sort per the 2.5.11 design (finding #57), composing the 2.5.13 reads. Scope split (re-planned 2026-06-05): this subtask is the LAZY + SORTABLE tree; virtualization is its own 2.5.15 (a separable DOM-perf layer) and the seed is 2.5.16 — so each lands as a reviewable PR, not a 1k-line sprawl.

  • Server-Action layer — a 'use server' wrapper over 2.5.13's listRootIssues/listChildIssues (session + active-project gate, like the shipped issue actions) so the client can fetch a level on expand / sort. The initial roots may load in the Server Component; children + re-sorts come through the action.
  • Lazy-expand — render roots; a parent's chevron shows from hasChildren without loading; expanding fetches listChildIssues (the design's spinner placeholder row on the node, aria-busy) and inserts them; a "Load more children" row at the children's indent when a parent exceeds the per-node page (50), appending the next page on click (parent never collapses).
  • Sortable headers — each column header is a sort button (the EXACT List affordance: caret + aria-sort asc/desc/none, not colour alone) that re-fetches via the sorted reads; siblings re-order WITHIN each parent (hierarchy preserved); shares the List's ?sort= URL shape. Already-expanded nodes re-fetch their children on a sort change.
  • Setsize count (read tweak) — the design wants each row's aria-setsize = its parent's TRUE child total, but 2.5.13's read returns only hasMore. Extend the lazy read/service to also return the per-node total child count (a cheap COUNT) so aria-posinset/aria-setsize are honest (e.g. 19 of 128) — needed now AND by 2.5.15's virtualization.

The accessible treegrid contract (2.5.2) + roving-tabindex keyboard model + the STRICT axe sweep must still pass; on a collapsed parent triggers its lazy fetch. A specimen route covers the sortable-header + loading + load-more states for the sweep.

(PR #115 merged 2026-06-05, merge commit 8ac519e — the /issues Tree is now LAZY + SORTABLE (finding #57). The Server Component loads only the first page of ROOTS (listRootIssues); children stream in on expand via listRootIssuesAction/listChildIssuesAction (spinner row + "Load more children" append, per-node page 50); column headers sort (caret + aria-sort, not colour alone) via ?sort=, re-ordering siblings within their parent. TreeTable extended backward-compatibly (per-row hasChildren/posinset/setsize overrides + busy/aria-busy + onRowActivate + per-column ariaSort); setsize count added to the 2.5.13 read (TreeLevelDto.total + countProjectTreeLevel) for honest aria-posinset/setsize. IssueTreeStaticTable keeps the FILTERED tree on the context-preserving whole-forest read (lazy+filter = Epic 6). Bug fixed: buildIssueListHref only emitted ?sort= for the List, so Tree sort wouldn't persist — now both views. Decisions (flagged for review): a sort change REMOUNTS the tree (expansion resets, not in-place re-fetch); the filtered tree is non-lazy/non-sortable; no new /tokens specimen (new states are component-tested + hit the strict a11y sweep on the real /issues route via 2.5.6). 4 new lazy-tree component tests + the count integration test; CI green (one flaky workflow-delete-reassign E2E — unrelated to this diff, passed on re-run). Split into 2.5.15 (virtualization) + 2.5.16 (db:seed:large).)

Acceptance criteria

  • Tree renders roots; a node lazy-loads its children on first expand (spinner row → real rows); "Load more children" appears + appends past the per-node page (50); collapsed parents never pre-load.
  • Column headers sort (caret + aria-sort, not colour alone); sorting re-orders siblings within each parent and persists in ?sort= like the List; expanded nodes re-fetch on sort change.
  • The lazy read/service returns the per-node total child count; rows carry honest aria-level/posinset/setsize/expanded; the treegrid a11y contract + STRICT axe sweep still pass + roving-tabindex keyboard (incl. triggers lazy fetch).
  • Component tests: expand→lazy-load→render, load-more append, header sort re-fetch; integration test for the count; tsc/eslint/prettier clean; next build compiles.
  • Virtualization is NOT in this subtask (it's 2.5.15) — but the row model + the DOM structure are built so windowing drops in without a rewrite (a flat ordered "visible rows" list the renderer maps over).

Context refs

  • components/ui/TreeTable.tsx (2.5.2) + app/(authed)/issues/_components/IssueTreeTable.tsx + IssueTreeSection.tsx — the rendering to rework
  • The 2.5.13 listRootIssues/listChildIssues reads (extend with the count) + the 2.5.11 design
  • The shipped List sort-header affordance (2.5.8 IssueListTable) + the ?sort= URL contract (issueListView) to mirror; the existing issue Server Actions for the action pattern
  • tests/e2e/shell-a11y.spec.ts tree-table strict sweep + the /tokens convention
Status
Done
Type
Sub-task