Files
lifeos-dev/lifeos-claude-code-prompts_R1_1.md

22 KiB

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:

    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.