feat: focus item detail page with inline note + checklist

Each standalone focus item now auto-creates a linked note and checklist.
Clicking a focus item opens a detail page with side-by-side note editor
(left) and checklist (right) with drag-to-reorder. Save & Return writes
the note and goes back to the focus list. Added focus_id FK to notes and
lists tables, made domain optional when creating from focus context.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 20:02:27 +00:00
parent a2183af6e2
commit 6abef336c4
9 changed files with 365 additions and 17 deletions

View File

@@ -63,6 +63,7 @@ async def create_form(
project_id: Optional[str] = None,
task_id: Optional[str] = None,
meeting_id: Optional[str] = None,
focus_id: Optional[str] = None,
db: AsyncSession = Depends(get_db),
):
sidebar = await get_sidebar_data(db)
@@ -79,6 +80,7 @@ async def create_form(
"prefill_project_id": project_id or "",
"prefill_task_id": task_id or "",
"prefill_meeting_id": meeting_id or "",
"prefill_focus_id": focus_id or "",
})
@@ -86,10 +88,11 @@ async def create_form(
async def create_note(
request: Request,
title: str = Form(...),
domain_id: str = Form(...),
domain_id: Optional[str] = Form(None),
project_id: Optional[str] = Form(None),
task_id: Optional[str] = Form(None),
meeting_id: Optional[str] = Form(None),
focus_id: Optional[str] = Form(None),
body: Optional[str] = Form(None),
content_format: str = Form("rich"),
tags: Optional[str] = Form(None),
@@ -97,15 +100,19 @@ async def create_note(
):
repo = BaseRepository("notes", db)
data = {
"title": title, "domain_id": domain_id,
"title": title,
"body": body, "content_format": content_format,
}
if domain_id and domain_id.strip():
data["domain_id"] = domain_id
if project_id and project_id.strip():
data["project_id"] = project_id
if task_id and task_id.strip():
data["task_id"] = task_id
if meeting_id and meeting_id.strip():
data["meeting_id"] = meeting_id
if focus_id and focus_id.strip():
data["focus_id"] = focus_id
if tags and tags.strip():
data["tags"] = [t.strip() for t in tags.split(",") if t.strip()]
note = await repo.create(data)
@@ -113,6 +120,8 @@ async def create_note(
return RedirectResponse(url=f"/tasks/{task_id}?tab=notes", status_code=303)
if meeting_id and meeting_id.strip():
return RedirectResponse(url=f"/meetings/{meeting_id}?tab=notes", status_code=303)
if focus_id and focus_id.strip():
return RedirectResponse(url=f"/focus/{focus_id}?tab=notes", status_code=303)
return RedirectResponse(url=f"/notes/{note['id']}", status_code=303)