feat: universal reorder grip handles and compact UI density

- Add generic move_in_order() to BaseRepository for reorder support
- Add reusable reorder_arrows.html partial with grip dot handles
- Add reorder routes to all 9 list routers (tasks, notes, links, contacts, meetings, decisions, appointments, lists, focus)
- Compact row padding (6px 12px, gap 8px) on .list-row and .focus-item
- Reduce font size to 0.80rem on row titles, sidebar nav, domain tree

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-03 01:45:02 +00:00
parent 27e07fefe1
commit 1628a4a748
22 changed files with 300 additions and 8 deletions

View File

@@ -248,3 +248,72 @@ class BaseRepository:
text(f"UPDATE {self.table} SET sort_order = :order WHERE id = :id"),
{"order": (i + 1) * 10, "id": str(id)}
)
async def swap_sort_order(self, id_a: str, id_b: str) -> None:
"""Swap sort_order between two rows."""
result = await self.db.execute(
text(f"SELECT id, sort_order FROM {self.table} WHERE id IN (:a, :b)"),
{"a": str(id_a), "b": str(id_b)},
)
rows = {str(r._mapping["id"]): r._mapping["sort_order"] for r in result}
if len(rows) != 2:
return
await self.db.execute(
text(f"UPDATE {self.table} SET sort_order = :order WHERE id = :id"),
{"order": rows[str(id_b)], "id": str(id_a)},
)
await self.db.execute(
text(f"UPDATE {self.table} SET sort_order = :order WHERE id = :id"),
{"order": rows[str(id_a)], "id": str(id_b)},
)
async def move_in_order(self, item_id: str, direction: str, filters: dict | None = None) -> None:
"""Move an item up or down within its sort group.
Handles lazy initialization (all sort_order=0) and swaps with neighbor.
filters: optional dict to scope the group (e.g. {"list_id": some_id}).
"""
where_clauses = ["is_deleted = false"]
params: dict[str, Any] = {}
if filters:
for i, (key, value) in enumerate(filters.items()):
if value is None:
where_clauses.append(f"{key} IS NULL")
else:
param_name = f"mf_{i}"
where_clauses.append(f"{key} = :{param_name}")
params[param_name] = value
where_sql = " AND ".join(where_clauses)
result = await self.db.execute(
text(f"SELECT id, sort_order FROM {self.table} WHERE {where_sql} ORDER BY sort_order, created_at"),
params,
)
items = [dict(r._mapping) for r in result]
if len(items) < 2:
return
# Lazy init: if all sort_order are 0, assign incremental values
if all(r["sort_order"] == 0 for r in items):
for i, r in enumerate(items):
await self.db.execute(
text(f"UPDATE {self.table} SET sort_order = :order WHERE id = :id"),
{"order": (i + 1) * 10, "id": str(r["id"])},
)
# Re-fetch
result = await self.db.execute(
text(f"SELECT id, sort_order FROM {self.table} WHERE {where_sql} ORDER BY sort_order, created_at"),
params,
)
items = [dict(r._mapping) for r in result]
ids = [str(r["id"]) for r in items]
if item_id not in ids:
return
idx = ids.index(item_id)
if direction == "up" and idx > 0:
await self.swap_sort_order(ids[idx], ids[idx - 1])
elif direction == "down" and idx < len(ids) - 1:
await self.swap_sort_order(ids[idx], ids[idx + 1])