Skip to content

Fractional Rank Algorithm

Custom ordering of Lists, Tasks, and Steps is persisted with fractional index keys rather than integer positions, so a reorder updates only the moved row. The algorithm lives in src/lib/rank/.

  • generateKeyBetween(prev, next) — produce a key that sorts strictly between two existing keys (either may be null for the ends). Inserting at the top of a List means generateKeyBetween(null, firstRank).
  • generateNKeysBetween(prev, next, n) — produce n evenly distributed keys between two bounds (bulk insert / backfill).
  • compareRanks(a, b) — total order over rank strings.
  • needsRebalance(rank, threshold = REBALANCE_THRESHOLD) — flag keys that have grown too long after many in-between insertions; REBALANCE_THRESHOLD is 64.
  • src/lib/rank/jitter.ts (getRandomBit) — adds a small random suffix so two clients inserting at the same position offline produce distinct keys, avoiding collisions on replay.

rank columns on lists, groups, tasks, and steps store the keys (migrations 0007_add_list_rank.sql, 0008_add_step_rank.sql). Ordering scope follows the domain rules: per-List for Tasks, per-Task for Steps, per-User for Lists and Groups (Product Brief §5.2). src/lib/rank/backfill.ts (run via scripts/backfill-ranks.mjs) assigns keys to pre-existing rows; src/lib/rank/sort-modes.ts maps the sort menu’s temporary orderings.

Sorting (importance / alphabetical / due date / created) produces a temporary view; drag-and-drop sets the persisted fractional order (spec 007).

Related: System Views & Sort Menus and the sidebar ordering UI (deferred guides).