# 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 `` 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.