338 lines
22 KiB
Markdown
338 lines
22 KiB
Markdown
# Life OS - Claude Code Prompts for DEV Enhancements
|
|
|
|
**Target Environment:** DEV only (lifeos-dev.invixiom.com)
|
|
**Codebase Location:** `/opt/lifeos/dev/`
|
|
**Hot Reload:** Changes to files in `/opt/lifeos/dev/` are picked up automatically by uvicorn `--reload`. No container rebuild needed.
|
|
**Database:** `lifeos_dev` on container `lifeos-db` (PostgreSQL 16)
|
|
|
|
---
|
|
|
|
## PROMPT 1: Task Detail Page - Add Tabs
|
|
|
|
**Context:** The task detail page at `/tasks/{task_id}` currently shows task metadata but does NOT use the tabbed layout. The project detail page at `/projects/{project_id}` (template: `project_detail.html`) IS the reference implementation for tabbed detail views. It uses a `?tab=` query parameter to switch between tabs, with tab content rendered server-side via Jinja2 conditionals.
|
|
|
|
**Codebase facts:**
|
|
- Stack: Python 3.12, FastAPI, SQLAlchemy async, Jinja2 templates, vanilla JS/CSS
|
|
- Template to modify: `templates/task_detail.html`
|
|
- Router to modify: `routers/tasks.py` (the `task_detail` GET handler at `/tasks/{task_id}`)
|
|
- Reference template: `templates/project_detail.html` - copy the tab-strip HTML structure and `?tab=` query param pattern exactly
|
|
- CSS: `static/style.css` already has `.tab-strip` and `.tab-content` classes from the project detail implementation
|
|
- BaseRepository pattern: `core/base_repository.py` provides generic CRUD. All queries use `is_deleted = false` filtering automatically.
|
|
|
|
**Existing DB tables/junctions (already in schema, may not have app code):**
|
|
- `notes` - has `domain_id`, `project_id` columns. Notes don't have a `task_id` FK directly. You'll need to either add a `task_id` FK column to notes OR use a polymorphic junction (e.g., `note_tasks` or use `file_mappings` pattern with `context_type='task'`). Prefer adding a nullable `task_id UUID REFERENCES tasks(id)` column to the `notes` table for simplicity.
|
|
- `weblinks` - has `domain_id`, `project_id`. Same situation as notes - add nullable `task_id UUID REFERENCES tasks(id)`.
|
|
- `file_mappings` - polymorphic junction: `file_id, context_type, context_id`. Already supports `context_type='task'`. Files tab should query `file_mappings WHERE context_type='task' AND context_id={task_id}` joined to `files`.
|
|
- `lists` - has `domain_id`, `project_id`. Add nullable `task_id UUID REFERENCES tasks(id)`.
|
|
- `decisions` - has `domain_id`, `project_id`. Add nullable `task_id UUID REFERENCES tasks(id)`.
|
|
- `processes` / `process_runs` - These tables exist in the R1 schema but process CRUD is NOT yet built. The Processes tab should show: (a) a way to apply a process template to this task (create a process_run with context), and (b) list existing process_runs linked to this task. For now, since process CRUD doesn't exist yet, create the tab structure with an empty state placeholder that says "Process management coming soon" and a TODO comment in the code.
|
|
- `contact_tasks` - junction table already exists with columns: `contact_id, task_id, role, created_at`. This is the Contacts + Roles tab data source.
|
|
|
|
**What to build:**
|
|
|
|
1. **Migration SQL** - Create a migration file at `/opt/lifeos/migrations/` that adds:
|
|
- `ALTER TABLE notes ADD COLUMN task_id UUID REFERENCES tasks(id);`
|
|
- `ALTER TABLE weblinks ADD COLUMN task_id UUID REFERENCES tasks(id);`
|
|
- `ALTER TABLE lists ADD COLUMN task_id UUID REFERENCES tasks(id);`
|
|
- `ALTER TABLE decisions ADD COLUMN task_id UUID REFERENCES tasks(id);`
|
|
- Create indexes on these new FK columns.
|
|
- Apply to `lifeos_dev` database via: `docker exec -i lifeos-db psql -U postgres -d lifeos_dev < /opt/lifeos/migrations/NNNN_task_detail_tabs.sql`
|
|
|
|
2. **Router** (`routers/tasks.py`) - Modify the task detail GET handler:
|
|
- Accept `tab` query parameter (default: "overview")
|
|
- Tabs: overview, notes, weblinks, files, lists, decisions, processes, contacts
|
|
- For each tab, query the relevant data and pass to template context
|
|
- For contacts tab: query `contact_tasks` joined to `contacts` WHERE `task_id = {id}`
|
|
- For files tab: query `file_mappings` WHERE `context_type='task' AND context_id={id}` joined to `files`
|
|
- For notes/weblinks/lists/decisions: query WHERE `task_id = {id} AND is_deleted = false`
|
|
|
|
3. **Template** (`templates/task_detail.html`) - Restructure to match `project_detail.html` tab pattern:
|
|
- Keep existing task header (title, metadata, action buttons including timer)
|
|
- Add tab-strip below header with all tab names
|
|
- Active tab highlighted via CSS class
|
|
- Tab links use `?tab=tabname` pattern
|
|
- Each tab section wrapped in Jinja2 `{% if tab == 'notes' %}...{% endif %}` blocks
|
|
- Overview tab contains what's currently shown (description, subtasks, metadata)
|
|
- Each entity tab shows a list of related items with an "+ Add" button
|
|
- Contacts tab shows contact name + role with ability to add/remove
|
|
|
|
4. **Contact + Role management on tasks:**
|
|
- Add endpoint `POST /tasks/{task_id}/contacts/add` accepting `contact_id` and `role` form fields. Insert into `contact_tasks`.
|
|
- Add endpoint `POST /tasks/{task_id}/contacts/{contact_id}/remove` to delete from `contact_tasks`.
|
|
- The contacts tab should show a form with a contact dropdown (all non-deleted contacts) and a role text input, plus a list of currently linked contacts with remove buttons.
|
|
- Role should be a free-text field (not a fixed set yet - we'll define role sets later).
|
|
|
|
5. **Update note/weblink/list/decision create forms** to accept optional `task_id` and pre-fill it when creating from within a task detail page. The "+ Add" buttons on each tab should link to the respective create form with `?task_id={task_id}` pre-filled.
|
|
|
|
**Do NOT:**
|
|
- Touch production database or files
|
|
- Build full process CRUD (just placeholder tab)
|
|
- Change the existing task list page (`tasks.html`)
|
|
- Add any npm/frontend build tooling
|
|
|
|
**Test:** After implementation, verify at `https://lifeos-dev.invixiom.com/tasks/{any_task_id}` that all tabs render, switching works via URL param, and the contacts add/remove flow works.
|
|
|
|
---
|
|
|
|
## PROMPT 2: Project Detail Page - Add Missing Tabs
|
|
|
|
**Context:** The project detail page at `/projects/{project_id}` already has a working tabbed layout with tabs for Tasks, Notes, Links (weblinks). It needs additional tabs for: Files, Lists, Processes, Contacts + Roles.
|
|
|
|
**Codebase facts:**
|
|
- Template: `templates/project_detail.html` (already has tab-strip pattern)
|
|
- Router: `routers/projects.py` (the detail GET handler already accepts `?tab=` param)
|
|
- Existing tabs: tasks, notes, links/weblinks
|
|
- `file_mappings` supports `context_type='project'` already
|
|
- `contact_projects` junction table exists: `contact_id, project_id, role, created_at`
|
|
- `lists` table has `project_id` FK already
|
|
- Process CRUD not yet built
|
|
|
|
**What to build:**
|
|
|
|
1. **Router** (`routers/projects.py`) - Extend the detail handler:
|
|
- Add tab cases for: files, lists, processes, contacts
|
|
- Files: query `file_mappings WHERE context_type='project' AND context_id={project_id}` joined to `files`
|
|
- Lists: query `lists WHERE project_id={id} AND is_deleted=false`
|
|
- Processes: placeholder (empty state)
|
|
- Contacts: query `contact_projects` joined to `contacts` WHERE `project_id={id}`
|
|
|
|
2. **Template** (`templates/project_detail.html`) - Add new tabs to existing tab-strip:
|
|
- Files tab: list of attached files with upload link (`/files/upload?context_type=project&context_id={project_id}`)
|
|
- Lists tab: list of project lists with link to create (`/lists/create?project_id={project_id}`)
|
|
- Processes tab: empty state placeholder "Process management coming soon"
|
|
- Contacts tab: same pattern as Task contacts - dropdown + role input + list with remove
|
|
|
|
3. **Contact + Role management on projects:**
|
|
- `POST /projects/{project_id}/contacts/add` - insert into `contact_projects`
|
|
- `POST /projects/{project_id}/contacts/{contact_id}/remove` - delete from `contact_projects`
|
|
- Same UI pattern as task contacts tab
|
|
|
|
**Do NOT:**
|
|
- Modify existing working tabs (tasks, notes, links)
|
|
- Build process CRUD
|
|
|
|
**Test:** Verify at `https://lifeos-dev.invixiom.com/projects/{any_project_id}` that new tabs appear and function.
|
|
|
|
---
|
|
|
|
## PROMPT 3: Meeting Detail Page - Add Tabs
|
|
|
|
**Context:** The meeting detail page at `/meetings/{meeting_id}` exists but may not have the full tabbed layout. It needs tabs for: Notes, Weblinks, Files, Lists, Processes, Contacts + Roles. Meetings already have an action items / tasks relationship via the `meeting_tasks` junction table.
|
|
|
|
**Codebase facts:**
|
|
- Template: `templates/meeting_detail.html`
|
|
- Router: `routers/meetings.py`
|
|
- `contact_meetings` junction exists: `contact_id, meeting_id, role, created_at` (role values: organizer|attendee|optional)
|
|
- `meeting_tasks` junction exists: `meeting_id, task_id, source` (source: discussed|action_item)
|
|
- `meetings` table has `notes_body` column for inline meeting notes
|
|
- Notes, weblinks, lists don't have a `meeting_id` FK column
|
|
|
|
**What to build:**
|
|
|
|
1. **Migration SQL** - Add to same or new migration file:
|
|
- `ALTER TABLE notes ADD COLUMN meeting_id UUID REFERENCES meetings(id);`
|
|
- `ALTER TABLE weblinks ADD COLUMN meeting_id UUID REFERENCES meetings(id);`
|
|
- `ALTER TABLE lists ADD COLUMN meeting_id UUID REFERENCES meetings(id);`
|
|
- Indexes on new columns.
|
|
- Apply to `lifeos_dev`.
|
|
|
|
2. **Router** (`routers/meetings.py`) - Add/extend detail handler with `?tab=` param:
|
|
- Tabs: overview (existing content: agenda, notes_body, action items), notes, weblinks, files, lists, processes, contacts
|
|
- Query patterns same as tasks/projects for each tab type
|
|
- Files via `file_mappings WHERE context_type='meeting'`
|
|
- Contacts via `contact_meetings` joined to `contacts`
|
|
|
|
3. **Template** (`templates/meeting_detail.html`) - Restructure to tab layout matching project_detail.html pattern:
|
|
- Overview tab: existing meeting content (agenda, meeting notes, action items list)
|
|
- All other tabs: same list + add pattern as task/project detail
|
|
|
|
4. **Contact + Role management on meetings:**
|
|
- `POST /meetings/{meeting_id}/contacts/add` - insert into `contact_meetings`, role defaults to "attendee"
|
|
- `POST /meetings/{meeting_id}/contacts/{contact_id}/remove` - delete from `contact_meetings`
|
|
- Role dropdown with predefined values: organizer, attendee, optional
|
|
|
|
**Test:** Verify at `https://lifeos-dev.invixiom.com/meetings/{any_meeting_id}`.
|
|
|
|
---
|
|
|
|
## PROMPT 4: Lists Page - Dynamic Domain/Project Filter + Contact Roles
|
|
|
|
**Context:** The lists page at `/lists/` has domain and project dropdown filters. Currently the project dropdown shows ALL projects regardless of which domain is selected. It should filter dynamically.
|
|
|
|
**Codebase facts:**
|
|
- Template: `templates/lists.html`
|
|
- Router: `routers/lists.py` (or wherever list CRUD lives)
|
|
- The filter uses HTML `<select>` elements that auto-submit on change (pattern from `app.js`)
|
|
- `projects` table has `domain_id` FK (nullable - some projects may have domain_id = NULL via unassigned area)
|
|
- `areas` table has `domain_id` FK
|
|
- `contact_lists` junction exists: `contact_id, list_id, role, created_at`
|
|
|
|
**What to build:**
|
|
|
|
1. **Dynamic project filtering by domain** - Two approaches, pick the simpler one:
|
|
- **Option A (recommended - vanilla JS):** Add a small JS block to `lists.html` (or `app.js`) that intercepts the domain dropdown `change` event. When domain changes, fetch projects for that domain via a new lightweight API endpoint `GET /api/projects?domain_id={id}` that returns JSON `[{id, name}]`. Rebuild the project `<select>` options dynamically. Include projects where `domain_id` matches OR where `domain_id IS NULL` (unassigned). If no domain selected, show all projects.
|
|
- **Option B (server-side only):** On domain change, the form auto-submits (existing behavior). The router filters the project dropdown options server-side based on the selected domain_id and passes filtered projects to the template. This is simpler but causes a page reload.
|
|
- Go with Option A for better UX. Create the API endpoint in the lists router or a shared API router.
|
|
|
|
2. **Contact + Role management on lists:**
|
|
- On the list detail page (`list_detail.html`), add a Contacts section (or tab if it has tabs).
|
|
- `POST /lists/{list_id}/contacts/add` - insert into `contact_lists`
|
|
- `POST /lists/{list_id}/contacts/{contact_id}/remove` - delete from `contact_lists`
|
|
- Same UI pattern: contact dropdown + role text input + list with remove buttons.
|
|
|
|
**Test:** Go to `https://lifeos-dev.invixiom.com/lists/`, select a domain, verify the project dropdown updates to only show projects in that domain plus unassigned ones. Test adding/removing contacts on a list detail page.
|
|
|
|
---
|
|
|
|
## PROMPT 5: Eisenhower Page - Dynamic Height + Filters
|
|
|
|
**Context:** The Eisenhower matrix page at `/eisenhower/` displays tasks in a 2x2 grid (Important+Urgent, Important+Not Urgent, Not Important+Urgent, Not Important+Not Urgent). Currently the quadrant cards have a fixed minimum height which wastes space or clips content.
|
|
|
|
**Codebase facts:**
|
|
- Template: `templates/eisenhower.html` (if it exists) or it may be part of another template
|
|
- Router: `routers/eisenhower.py` or may be in `routers/tasks.py`
|
|
- CSS in `static/style.css`
|
|
- Current quadrant logic: priority 1-2 = Important, 3-4 = Not Important. Due date <= 7 days = Urgent, > 7 days or null = Not Urgent.
|
|
- Tasks are filtered: only `status IN ('open', 'in_progress', 'blocked')` and `is_deleted = false`
|
|
|
|
**What to build:**
|
|
|
|
1. **Dynamic height for quadrant cards:**
|
|
- Remove any `min-height` or fixed `height` CSS on the Eisenhower quadrant containers.
|
|
- Use CSS that allows quadrants to grow with content: `min-height: 200px` (reasonable minimum) but no `max-height` restriction. Or use CSS Grid with `grid-template-rows: auto auto` so rows size to content.
|
|
- Each card within a quadrant should be compact (task title, priority dot, due date - single line or two lines max).
|
|
- If a quadrant has many tasks, it grows. If empty, it shows a small empty state.
|
|
|
|
2. **Add filters:**
|
|
- Add a filter bar above the 2x2 grid with dropdowns for:
|
|
- **Domain** - filter tasks by domain_id
|
|
- **Project** - filter tasks by project_id (dynamic based on domain selection, same pattern as Prompt 4)
|
|
- **Status** - filter by status (open, in_progress, blocked)
|
|
- **Context** - filter by context (GTD execution context from context_types table)
|
|
- Filters use query parameters: `/eisenhower/?domain_id=X&project_id=Y&status=Z&context=W`
|
|
- Router applies these filters to the task query before distributing into quadrants.
|
|
- Use the same auto-submit-on-change pattern used elsewhere in the app.
|
|
|
|
**Test:** Verify at `https://lifeos-dev.invixiom.com/eisenhower/` that quadrants resize with content and filters narrow the displayed tasks.
|
|
|
|
---
|
|
|
|
## PROMPT 6: Focus Page - Add Domain/Area/Project Filters
|
|
|
|
**Context:** The focus page at `/focus/` shows the daily focus list (tasks selected for today). Currently there are no filters to narrow which available tasks are shown for adding to focus.
|
|
|
|
**Codebase facts:**
|
|
- Template: `templates/focus.html`
|
|
- Router: `routers/focus.py`
|
|
- The page has two sections: (1) Today's focus items (tasks already added), (2) Available tasks to add
|
|
- `daily_focus` table: `id, task_id, focus_date, completed, sort_order, is_deleted, created_at`
|
|
- Tasks have `domain_id`, `project_id` FKs. Tasks also have `area_id` FK.
|
|
|
|
**What to build:**
|
|
|
|
1. **Filter bar for available tasks section:**
|
|
- Add dropdowns above the "Available Tasks" section for: Domain, Area (filtered by selected domain), Project (filtered by selected domain/area)
|
|
- Same dynamic filtering pattern as Prompts 4 and 5
|
|
- Filters apply only to the "available tasks to add" list, NOT to the already-focused items
|
|
- Query params: `/focus/?domain_id=X&area_id=Y&project_id=Z&focus_date=YYYY-MM-DD`
|
|
|
|
2. **Router changes:**
|
|
- Accept domain_id, area_id, project_id query params
|
|
- Apply as WHERE clauses when querying available (unfocused) tasks
|
|
- Pass filter values + dropdown options to template context
|
|
|
|
**Test:** Verify at `https://lifeos-dev.invixiom.com/focus/` that filter dropdowns appear and narrow the available tasks list.
|
|
|
|
---
|
|
|
|
## PROMPT 7: Change History (Recently Changed Items)
|
|
|
|
**Context:** We need a "Change History" view so the user can see recently modified items across the entire system. Every table in Life OS has `created_at` and `updated_at` TIMESTAMPTZ columns (except `time_entries` which is missing `updated_at`). This gives us a simple approach: query `updated_at` across all entity tables and show a reverse-chronological feed.
|
|
|
|
**Codebase facts:**
|
|
- All main entity tables have `updated_at`: domains, areas, projects, tasks, notes, contacts, meetings, decisions, lists, weblinks, appointments, links, files, processes, capture
|
|
- `time_entries` does NOT have `updated_at` - exclude from change history
|
|
- BaseRepository handles all CRUD and sets `updated_at = now()` on every update
|
|
- Sidebar navigation is built in `core/sidebar.py`
|
|
|
|
**What to build:**
|
|
|
|
1. **New router:** Create `routers/history.py` with prefix `/history`
|
|
- `GET /history/` - renders the change history page
|
|
- Query: For each entity table, `SELECT id, 'entity_type' as type, title/name as label, updated_at, created_at FROM {table} WHERE is_deleted = false ORDER BY updated_at DESC LIMIT 20`
|
|
- Union all results, sort by `updated_at DESC`, take top 50 (or paginate)
|
|
- For each item, determine if it was "created" (updated_at = created_at or within 1 second) or "modified"
|
|
- Pass to template as a list of `{type, id, label, updated_at, action}` dicts
|
|
|
|
2. **Template:** Create `templates/history.html`
|
|
- Page title: "Change History" or "Recent Changes"
|
|
- Filter by: entity type dropdown, date range
|
|
- Each row shows: timestamp, action icon (created/modified), entity type badge, item name as clickable link to detail page, relative time ("2 minutes ago", "yesterday")
|
|
- Use the standard list-row styling
|
|
|
|
3. **Sidebar link:** Add "History" to the sidebar navigation in `core/sidebar.py` (or `base.html` directly), in the utility section near Search/Capture/Admin.
|
|
|
|
4. **Register router** in `main.py`: `app.include_router(history.router)`
|
|
|
|
**Architecture note:** This is NOT a full audit log with field-level diffs. It's a simple "what changed recently" view derived from `updated_at` timestamps. A full audit log (with before/after values) would require triggers or middleware - defer that to a later phase.
|
|
|
|
**Test:** Verify at `https://lifeos-dev.invixiom.com/history/` that recently created/modified items appear in reverse chronological order with working links.
|
|
|
|
---
|
|
|
|
## PROMPT 8: Fix Search - Partial Word Matching
|
|
|
|
**Context:** The global search currently uses PostgreSQL `tsvector/tsquery` full-text search. This is great for natural language search but does NOT support partial word matching. Searching for "Sys" does not return items containing "System" because tsquery requires complete lexemes.
|
|
|
|
**Codebase facts:**
|
|
- Search router: `routers/search.py`
|
|
- API endpoint: `GET /search/api?q=X&entity_type=Y&limit=Z`
|
|
- Page endpoint: `GET /search/?q=X`
|
|
- Every searchable table has a `search_vector TSVECTOR` column with GIN index
|
|
- Search triggers maintain the tsvector automatically
|
|
- Current query pattern likely uses `plainto_tsquery('english', query)` or `to_tsquery`
|
|
|
|
**The fix:**
|
|
|
|
The search needs to support partial prefix matching. PostgreSQL supports this via `:*` suffix on tsquery terms.
|
|
|
|
1. **Modify the search query building** in `routers/search.py`:
|
|
- Instead of `plainto_tsquery('english', query)`, build a prefix query
|
|
- For a search term like "Sys", construct: `to_tsquery('english', 'Sys:*')`
|
|
- For multi-word queries like "Sys Admin", construct: `to_tsquery('english', 'Sys:* & Admin:*')` (AND all terms with prefix matching)
|
|
- Implementation: split the query string on whitespace, strip non-alphanumeric chars from each term, append `:*` to each, join with ` & `
|
|
- Handle edge cases: empty query, single character (skip search for < 2 chars), special characters
|
|
|
|
2. **Also add an ILIKE fallback** for when tsvector doesn't match (tsvector strips stop words and stems, so very short or unusual terms might not match):
|
|
- After the tsvector query, if results are sparse (< 3 results), do a supplemental `WHERE title ILIKE '%{query}%' OR name ILIKE '%{query}%'` query
|
|
- Deduplicate results (tsvector results first, then ILIKE additions)
|
|
- This ensures "Sys" matches "System" even if the stemmer does something unexpected
|
|
|
|
3. **Update the search query for EACH entity** that's searched. The pattern should be consistent - build a helper function like:
|
|
```python
|
|
def build_search_condition(query: str):
|
|
terms = query.strip().split()
|
|
if not terms:
|
|
return None
|
|
tsquery_str = ' & '.join(f"{term}:*" for term in terms if len(term) >= 2)
|
|
return tsquery_str
|
|
```
|
|
|
|
**Do NOT:**
|
|
- Change the search_vector triggers or GIN indexes (they're fine)
|
|
- Add any new database tables
|
|
- Change the search UI/template (just the backend query logic)
|
|
|
|
**Test:** Go to `https://lifeos-dev.invixiom.com/search/?q=Sys` and verify it returns items containing "System", "Systematic", "Sys" etc. Test with "Inv" to match "Invoice", "Investment" etc.
|
|
|
|
---
|
|
|
|
## General Notes for All Prompts
|
|
|
|
- **Git:** After each prompt's changes are verified working, run: `cd /opt/lifeos/dev && git add . && git commit -m "descriptive message" && git push origin main`
|
|
- **Database migrations:** Always apply to `lifeos_dev` only. Command pattern: `docker exec -i lifeos-db psql -U postgres -d lifeos_dev < migration_file.sql`
|
|
- **Restart if needed:** `docker restart lifeos-dev` (though hot reload should handle most changes)
|
|
- **Logs:** `docker logs lifeos-dev --tail 30` to debug errors
|
|
- **Contact role pattern:** For now, roles are free-text on tasks/projects/lists. On meetings, roles are constrained to: organizer, attendee, optional. This will be standardized later with a defined role set.
|
|
- **Empty states:** Every tab/section that can be empty should show a clean empty state message with a CTA button to add the first item.
|