diff --git a/core/base_repository.py b/core/base_repository.py
index eaf77f9..3b9d614 100644
--- a/core/base_repository.py
+++ b/core/base_repository.py
@@ -162,7 +162,7 @@ class BaseRepository:
"contact_id", "started_at",
"weekly_hours", "effective_from",
"task_id", "meeting_id", "list_item_id",
- "domain_id", "title",
+ "domain_id", "title", "focus_priority",
}
clean_data = {}
for k, v in data.items():
diff --git a/routers/focus.py b/routers/focus.py
index 36730a8..aa392d0 100644
--- a/routers/focus.py
+++ b/routers/focus.py
@@ -63,7 +63,7 @@ async def focus_view(
WHERE df.is_deleted = false
AND (t.id IS NULL OR t.is_deleted = false)
AND (li.id IS NULL OR li.is_deleted = false)
- ORDER BY df.sort_order, df.created_at
+ ORDER BY df.focus_priority ASC NULLS LAST, df.sort_order, df.created_at
"""))
items = [dict(r._mapping) for r in result]
@@ -344,10 +344,23 @@ async def focus_detail(
"""), {"lid": list_id})
list_items = [dict(r._mapping) for r in result]
+ # Load linked links
+ result = await db.execute(text("""
+ SELECT l.*, fl.role
+ FROM links l JOIN focus_links fl ON fl.link_id = l.id
+ WHERE fl.focus_id = :fid AND l.is_deleted = false
+ ORDER BY fl.created_at
+ """), {"fid": focus_id})
+ linked_links = [dict(r._mapping) for r in result]
+
+ # All links for the "add existing" dropdown
+ all_links = await BaseRepository("links", db).list()
+
return templates.TemplateResponse("focus_detail.html", {
"request": request, "sidebar": sidebar, "item": item,
"domain": domain, "project": project, "all_lists": all_lists,
"note": note, "list_id": list_id, "list_items": list_items,
+ "linked_links": linked_links, "all_links": all_links,
"page_title": item.get("title", "Focus Item"), "active_nav": "focus",
})
@@ -392,6 +405,17 @@ async def toggle_focus_list_item(
return RedirectResponse(url=f"/focus/{focus_id}", status_code=303)
+@router.post("/{focus_id}/list-item/{item_id}/edit")
+async def edit_focus_list_item(
+ focus_id: str, item_id: str, request: Request,
+ content: str = Form(...),
+ db: AsyncSession = Depends(get_db),
+):
+ li_repo = BaseRepository("list_items", db)
+ await li_repo.update(item_id, {"content": content.strip()})
+ return RedirectResponse(url=f"/focus/{focus_id}", status_code=303)
+
+
@router.post("/{focus_id}/list-item/{item_id}/delete")
async def delete_focus_list_item(
focus_id: str, item_id: str, request: Request,
@@ -600,6 +624,44 @@ async def toggle_focus(focus_id: str, request: Request, db: AsyncSession = Depen
return RedirectResponse(url=referer, status_code=303)
+@router.post("/{focus_id}/set-priority")
+async def set_focus_priority(focus_id: str, request: Request, focus_priority: Optional[str] = Form(None), db: AsyncSession = Depends(get_db)):
+ repo = BaseRepository("daily_focus", db)
+ val = None
+ if focus_priority and focus_priority.strip():
+ try:
+ val = int(focus_priority.strip())
+ except ValueError:
+ pass
+ await repo.update(focus_id, {"focus_priority": val})
+ return RedirectResponse(url="/focus", status_code=303)
+
+
+@router.post("/{focus_id}/links/add")
+async def add_focus_link(
+ focus_id: str,
+ link_id: str = Form(...),
+ role: Optional[str] = Form(None),
+ db: AsyncSession = Depends(get_db),
+):
+ await db.execute(text("""
+ INSERT INTO focus_links (focus_id, link_id, role)
+ VALUES (:fid, :lid, :role) ON CONFLICT DO NOTHING
+ """), {"fid": focus_id, "lid": link_id, "role": role if role and role.strip() else None})
+ return RedirectResponse(url=f"/focus/{focus_id}", status_code=303)
+
+
+@router.post("/{focus_id}/links/{link_id}/remove")
+async def remove_focus_link(
+ focus_id: str, link_id: str,
+ db: AsyncSession = Depends(get_db),
+):
+ await db.execute(text(
+ "DELETE FROM focus_links WHERE focus_id = :fid AND link_id = :lid"
+ ), {"fid": focus_id, "lid": link_id})
+ return RedirectResponse(url=f"/focus/{focus_id}", status_code=303)
+
+
@router.post("/{focus_id}/toggle-critical")
async def toggle_critical(focus_id: str, request: Request, db: AsyncSession = Depends(get_db)):
repo = BaseRepository("daily_focus", db)
diff --git a/routers/links.py b/routers/links.py
index 32b334e..bb4701a 100644
--- a/routers/links.py
+++ b/routers/links.py
@@ -53,6 +53,7 @@ async def create_form(
task_id: Optional[str] = None,
meeting_id: Optional[str] = None,
contact_id: Optional[str] = None,
+ focus_id: Optional[str] = None,
db: AsyncSession = Depends(get_db),
):
sidebar = await get_sidebar_data(db)
@@ -69,6 +70,7 @@ async def create_form(
"prefill_task_id": task_id or "",
"prefill_meeting_id": meeting_id or "",
"prefill_contact_id": contact_id or "",
+ "prefill_focus_id": focus_id or "",
})
@@ -78,6 +80,7 @@ async def create_link(
domain_id: Optional[str] = Form(None), project_id: Optional[str] = Form(None),
task_id: Optional[str] = Form(None), meeting_id: Optional[str] = Form(None),
contact_id: Optional[str] = Form(None),
+ focus_id: Optional[str] = Form(None),
description: Optional[str] = Form(None), tags: Optional[str] = Form(None),
db: AsyncSession = Depends(get_db),
):
@@ -111,6 +114,15 @@ async def create_link(
await db.commit()
return RedirectResponse(url=f"/contacts/{contact_id}", status_code=303)
+ # Attach to focus item if created from focus context
+ if focus_id and focus_id.strip():
+ await db.execute(text("""
+ INSERT INTO focus_links (focus_id, link_id)
+ VALUES (:fid, :lid) ON CONFLICT DO NOTHING
+ """), {"fid": focus_id, "lid": link["id"]})
+ await db.commit()
+ return RedirectResponse(url=f"/focus/{focus_id}", status_code=303)
+
# Redirect back to context if created from task/meeting/project
if task_id and task_id.strip():
return RedirectResponse(url=f"/tasks/{task_id}?tab=links", status_code=303)
diff --git a/routers/lists.py b/routers/lists.py
index 7db9aa1..fb88839 100644
--- a/routers/lists.py
+++ b/routers/lists.py
@@ -375,6 +375,19 @@ async def reorder_list(
return RedirectResponse(url=request.headers.get("referer", "/lists"), status_code=303)
+@router.post("/reorder-all")
+async def reorder_all_lists(
+ request: Request,
+ item_ids: str = Form(...),
+ db: AsyncSession = Depends(get_db),
+):
+ repo = BaseRepository("lists", db)
+ ids = [i.strip() for i in item_ids.split(",") if i.strip()]
+ if ids:
+ await repo.reorder(ids)
+ 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,
diff --git a/routers/tasks.py b/routers/tasks.py
index c995d54..aa1d648 100644
--- a/routers/tasks.py
+++ b/routers/tasks.py
@@ -235,6 +235,7 @@ async def task_detail(
# Tab-specific data
tab_data = []
all_contacts = []
+ all_lists = []
if tab == "notes":
result = await db.execute(text("""
@@ -268,6 +269,13 @@ async def task_detail(
ORDER BY l.sort_order, l.created_at DESC
"""), {"tid": task_id})
tab_data = [dict(r._mapping) for r in result]
+ # Available lists for add dropdown (not already assigned to this task)
+ result = await db.execute(text("""
+ SELECT id, name FROM lists
+ WHERE is_deleted = false AND (task_id IS NULL OR task_id != :tid)
+ ORDER BY name
+ """), {"tid": task_id})
+ all_lists = [dict(r._mapping) for r in result]
elif tab == "decisions":
result = await db.execute(text("""
@@ -315,7 +323,7 @@ async def task_detail(
"request": request, "sidebar": sidebar, "item": item,
"domain": domain, "project": project, "parent": parent,
"subtasks": subtasks, "tab": tab, "tab_data": tab_data,
- "all_contacts": all_contacts, "counts": counts,
+ "all_contacts": all_contacts, "all_lists": all_lists, "counts": counts,
"running_task_id": running_task_id,
"page_title": item["title"], "active_nav": "tasks",
})
@@ -471,6 +479,31 @@ async def quick_add(
# ---- Contact linking ----
+@router.post("/{task_id}/lists/add")
+async def add_list_to_task(
+ task_id: str,
+ list_id: str = Form(...),
+ db: AsyncSession = Depends(get_db),
+):
+ await db.execute(text(
+ "UPDATE lists SET task_id = :tid, updated_at = now() WHERE id = :lid"
+ ), {"tid": task_id, "lid": list_id})
+ await db.commit()
+ return RedirectResponse(url=f"/tasks/{task_id}?tab=lists", status_code=303)
+
+
+@router.post("/{task_id}/lists/{list_id}/remove")
+async def remove_list_from_task(
+ task_id: str, list_id: str,
+ db: AsyncSession = Depends(get_db),
+):
+ await db.execute(text(
+ "UPDATE lists SET task_id = NULL, updated_at = now() WHERE id = :lid AND task_id = :tid"
+ ), {"tid": task_id, "lid": list_id})
+ await db.commit()
+ return RedirectResponse(url=f"/tasks/{task_id}?tab=lists", status_code=303)
+
+
@router.post("/{task_id}/contacts/add")
async def add_contact(
task_id: str,
diff --git a/templates/focus.html b/templates/focus.html
index 631a6cc..f60ff88 100644
--- a/templates/focus.html
+++ b/templates/focus.html
@@ -29,7 +29,7 @@
- #
+ P#
Done
!
@@ -46,7 +46,11 @@