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

@@ -300,3 +300,15 @@ async def delete_appointment(
repo = BaseRepository("appointments", db)
await repo.soft_delete(appointment_id)
return RedirectResponse(url="/appointments", status_code=303)
@router.post("/reorder")
async def reorder_appointment(
request: Request,
item_id: str = Form(...),
direction: str = Form(...),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("appointments", db)
await repo.move_in_order(item_id, direction)
return RedirectResponse(url=request.headers.get("referer", "/appointments"), status_code=303)

View File

@@ -124,3 +124,15 @@ async def delete_contact(contact_id: str, db: AsyncSession = Depends(get_db)):
repo = BaseRepository("contacts", db)
await repo.soft_delete(contact_id)
return RedirectResponse(url="/contacts", status_code=303)
@router.post("/reorder")
async def reorder_contact(
request: Request,
item_id: str = Form(...),
direction: str = Form(...),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("contacts", db)
await repo.move_in_order(item_id, direction)
return RedirectResponse(url=request.headers.get("referer", "/contacts"), status_code=303)

View File

@@ -216,3 +216,15 @@ async def delete_decision(decision_id: str, request: Request, db: AsyncSession =
repo = BaseRepository("decisions", db)
await repo.soft_delete(decision_id)
return RedirectResponse(url="/decisions", status_code=303)
@router.post("/reorder")
async def reorder_decision(
request: Request,
item_id: str = Form(...),
direction: str = Form(...),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("decisions", db)
await repo.move_in_order(item_id, direction)
return RedirectResponse(url=request.headers.get("referer", "/decisions"), status_code=303)

View File

@@ -121,6 +121,20 @@ async def add_to_focus(
return RedirectResponse(url=f"/focus?focus_date={focus_date}", status_code=303)
@router.post("/reorder")
async def reorder_focus(
request: Request,
item_id: str = Form(...),
direction: str = Form(...),
focus_date: str = Form(...),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("daily_focus", db)
parsed_date = date.fromisoformat(focus_date)
await repo.move_in_order(item_id, direction, filters={"focus_date": parsed_date})
return RedirectResponse(url=f"/focus?focus_date={focus_date}", status_code=303)
@router.post("/{focus_id}/toggle")
async def toggle_focus(focus_id: str, request: Request, db: AsyncSession = Depends(get_db)):
repo = BaseRepository("daily_focus", db)

View File

@@ -154,3 +154,15 @@ async def delete_link(link_id: str, request: Request, db: AsyncSession = Depends
repo = BaseRepository("links", db)
await repo.soft_delete(link_id)
return RedirectResponse(url=request.headers.get("referer", "/links"), status_code=303)
@router.post("/reorder")
async def reorder_link(
request: Request,
item_id: str = Form(...),
direction: str = Form(...),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("links", db)
await repo.move_in_order(item_id, direction)
return RedirectResponse(url=request.headers.get("referer", "/links"), status_code=303)

View File

@@ -332,3 +332,35 @@ async def remove_contact(
"DELETE FROM contact_lists WHERE contact_id = :cid AND list_id = :lid"
), {"cid": contact_id, "lid": list_id})
return RedirectResponse(url=f"/lists/{list_id}", status_code=303)
@router.post("/reorder")
async def reorder_list(
request: Request,
item_id: str = Form(...),
direction: str = Form(...),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("lists", db)
await repo.move_in_order(item_id, direction)
return RedirectResponse(url=request.headers.get("referer", "/lists"), status_code=303)
@router.post("/{list_id}/items/reorder")
async def reorder_list_item(
list_id: str,
request: Request,
item_id: str = Form(...),
direction: str = Form(...),
parent_id: Optional[str] = Form(None),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("list_items", db)
filters = {"list_id": list_id}
if parent_id:
filters["parent_item_id"] = parent_id
else:
# Top-level items only (no parent)
filters["parent_item_id"] = None
await repo.move_in_order(item_id, direction, filters=filters)
return RedirectResponse(url=f"/lists/{list_id}", status_code=303)

View File

@@ -404,3 +404,15 @@ async def remove_contact(
"DELETE FROM contact_meetings WHERE contact_id = :cid AND meeting_id = :mid"
), {"cid": contact_id, "mid": meeting_id})
return RedirectResponse(url=f"/meetings/{meeting_id}?tab=contacts", status_code=303)
@router.post("/reorder")
async def reorder_meeting(
request: Request,
item_id: str = Form(...),
direction: str = Form(...),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("meetings", db)
await repo.move_in_order(item_id, direction)
return RedirectResponse(url=request.headers.get("referer", "/meetings"), status_code=303)

View File

@@ -193,3 +193,15 @@ async def delete_note(note_id: str, request: Request, db: AsyncSession = Depends
await repo.soft_delete(note_id)
referer = request.headers.get("referer", "/notes")
return RedirectResponse(url=referer, status_code=303)
@router.post("/reorder")
async def reorder_note(
request: Request,
item_id: str = Form(...),
direction: str = Form(...),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("notes", db)
await repo.move_in_order(item_id, direction)
return RedirectResponse(url=request.headers.get("referer", "/notes"), status_code=303)

View File

@@ -481,3 +481,15 @@ async def remove_contact(
"DELETE FROM contact_tasks WHERE contact_id = :cid AND task_id = :tid"
), {"cid": contact_id, "tid": task_id})
return RedirectResponse(url=f"/tasks/{task_id}?tab=contacts", status_code=303)
@router.post("/reorder")
async def reorder_task(
request: Request,
item_id: str = Form(...),
direction: str = Form(...),
db: AsyncSession = Depends(get_db),
):
repo = BaseRepository("tasks", db)
await repo.move_in_order(item_id, direction)
return RedirectResponse(url=request.headers.get("referer", "/tasks"), status_code=303)