Initial commit
This commit is contained in:
443
CLAUDE.md
Normal file
443
CLAUDE.md
Normal file
@@ -0,0 +1,443 @@
|
||||
# CLAUDE.md
|
||||
|
||||
## Project Identity
|
||||
|
||||
Life OS is a single-user personal productivity web app. Server-rendered monolith. No SPA, no frontend framework, no build pipeline. You are working on the codebase directly on the production server via mounted Docker volume.
|
||||
|
||||
**Live URL:** https://lifeos-dev.invixiom.com
|
||||
**Server:** defiant-01, 46.225.166.142, Ubuntu 24.04
|
||||
**Repo:** github.com/mdombaugh/lifeos-dev (main branch)
|
||||
|
||||
---
|
||||
|
||||
## Tech Stack - Do Not Deviate
|
||||
|
||||
- **Python 3.12 / FastAPI** (async) / **SQLAlchemy 2.0 async** with raw SQL via `text()` - **NO ORM models**
|
||||
- **PostgreSQL 16** in Docker container `lifeos-db`, full-text search via tsvector/tsquery
|
||||
- **Jinja2** server-rendered HTML templates
|
||||
- **Vanilla CSS/JS** only. No npm. No React. No Tailwind. No build tools.
|
||||
- **asyncpg** driver
|
||||
- CSS custom properties for dark/light theme (`[data-theme='dark']` / `[data-theme='light']`)
|
||||
|
||||
**Hard rules:**
|
||||
- No fetch/XHR from JavaScript. Forms use standard HTML POST with 303 redirect (PRG pattern).
|
||||
- JavaScript handles ONLY UI state: sidebar collapse, search modal, timer display, theme toggle, drag-to-reorder.
|
||||
- All data operations go through BaseRepository or raw SQL. Never introduce an ORM model layer.
|
||||
|
||||
---
|
||||
|
||||
## Working Directory
|
||||
|
||||
All application code lives at `/opt/lifeos/dev/` on the server, mounted into the Docker container as `/app`. The container runs `uvicorn main:app --host 0.0.0.0 --port 8003 --workers 1 --reload`, so any file change is picked up immediately. No container rebuild needed.
|
||||
|
||||
```
|
||||
/opt/lifeos/dev/
|
||||
main.py # FastAPI app init, 18 router includes
|
||||
.env # DB creds (never commit)
|
||||
Dockerfile
|
||||
requirements.txt
|
||||
core/
|
||||
__init__.py
|
||||
database.py # Async engine, session factory, get_db dependency
|
||||
base_repository.py # Generic CRUD with soft deletes
|
||||
sidebar.py # Domain > area > project nav tree + badge counts
|
||||
routers/
|
||||
domains.py, areas.py, projects.py, tasks.py
|
||||
notes.py, links.py, focus.py, capture.py, contacts.py
|
||||
search.py, admin.py, lists.py
|
||||
files.py, meetings.py, decisions.py, weblinks.py
|
||||
appointments.py, time_tracking.py
|
||||
templates/
|
||||
base.html # Shell: topbar, sidebar, JS includes
|
||||
dashboard.html, search.html, trash.html
|
||||
[entity].html, [entity]_form.html, [entity]_detail.html
|
||||
(42 templates total - all extend base.html)
|
||||
static/
|
||||
style.css # ~1040 lines
|
||||
app.js # ~190 lines
|
||||
tests/
|
||||
introspect.py # Route discovery engine
|
||||
form_factory.py # Form data generation
|
||||
registry.py # Route registry + seed mapping
|
||||
conftest.py # Fixtures: DB, client, 15 seed entities
|
||||
test_smoke_dynamic.py # Auto-parametrized GET tests
|
||||
test_crud_dynamic.py # Auto-parametrized POST tests
|
||||
test_business_logic.py # Hand-written behavioral tests
|
||||
route_report.py # CLI route dump
|
||||
run_tests.sh # Test runner
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to Make Changes
|
||||
|
||||
**Read before writing.** Before modifying any file, read it first. The codebase has consistent patterns. Match them exactly.
|
||||
|
||||
**Do not create deploy scripts or heredoc wrappers.** You have direct filesystem access to `/opt/lifeos/dev/`. Edit files in place. Hot reload handles the rest.
|
||||
|
||||
**After changes, verify:**
|
||||
```bash
|
||||
docker logs lifeos-dev --tail 10 # Check for import/syntax errors
|
||||
curl -s -o /dev/null -w "%{http_code}" http://localhost:8003/ # Should be 200
|
||||
```
|
||||
|
||||
**Commit when asked:**
|
||||
```bash
|
||||
cd /opt/lifeos/dev && git add . && git commit -m "description" && git push origin main
|
||||
```
|
||||
Push uses PAT (personal access token) as password.
|
||||
|
||||
---
|
||||
|
||||
## Database Access
|
||||
|
||||
```bash
|
||||
# Query
|
||||
docker exec lifeos-db psql -U postgres -d lifeos_dev -c "SQL HERE"
|
||||
|
||||
# Inspect table schema (DO THIS before writing code that touches a table)
|
||||
docker exec lifeos-db psql -U postgres -d lifeos_dev -c "\d table_name"
|
||||
|
||||
# List all tables
|
||||
docker exec lifeos-db psql -U postgres -d lifeos_dev -c "\dt"
|
||||
```
|
||||
|
||||
**Connection string (in .env):**
|
||||
```
|
||||
DATABASE_URL=postgresql+asyncpg://postgres:UCTOQDZiUhN8U@lifeos-db:5432/lifeos_dev
|
||||
FILE_STORAGE_PATH=/opt/lifeos/files/dev
|
||||
ENVIRONMENT=development
|
||||
```
|
||||
|
||||
**Three databases exist in lifeos-db:**
|
||||
- `lifeos_dev` - Active development (this is what you work with)
|
||||
- `lifeos_prod` - Production data (DO NOT TOUCH)
|
||||
- `lifeos_test` - Test suite database
|
||||
|
||||
**Always verify table schemas against the live DB before writing code.** The .sql files in the project-docs folder may be stale. The database is the source of truth:
|
||||
```bash
|
||||
docker exec lifeos-db psql -U postgres -d lifeos_dev -c "\d tasks"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backup Before Risky Changes
|
||||
|
||||
```bash
|
||||
mkdir -p /opt/lifeos/backups
|
||||
docker exec lifeos-db pg_dump -U postgres -d lifeos_dev -Fc -f /tmp/lifeos_dev_backup.dump
|
||||
docker cp lifeos-db:/tmp/lifeos_dev_backup.dump /opt/lifeos/backups/lifeos_dev_$(date +%Y%m%d_%H%M%S).dump
|
||||
```
|
||||
|
||||
**Restore:**
|
||||
```bash
|
||||
docker exec lifeos-db psql -U postgres -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = 'lifeos_dev' AND pid <> pg_backend_pid();"
|
||||
docker exec lifeos-db psql -U postgres -c "DROP DATABASE lifeos_dev;"
|
||||
docker exec lifeos-db psql -U postgres -c "CREATE DATABASE lifeos_dev;"
|
||||
docker cp /opt/lifeos/backups/FILENAME.dump lifeos-db:/tmp/restore.dump
|
||||
docker exec lifeos-db pg_restore -U postgres -d lifeos_dev /tmp/restore.dump
|
||||
docker restart lifeos-dev
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## BaseRepository Pattern - Use It
|
||||
|
||||
All CRUD goes through `core/base_repository.py`. Read this file to understand the API. Key methods:
|
||||
|
||||
- `list(filters={}, sort='sort_order')` - Auto-filters `is_deleted=false`
|
||||
- `get(id)` - Single record by UUID
|
||||
- `create(data)` - Auto-sets created_at, updated_at, is_deleted=false
|
||||
- `update(id, data)` - Auto-sets updated_at
|
||||
- `soft_delete(id)` - Sets is_deleted=true, deleted_at=now()
|
||||
- `restore(id)` - Reverses soft_delete
|
||||
- `permanent_delete(id)` - Actual SQL DELETE (admin only)
|
||||
- `bulk_soft_delete(ids)`, `reorder(ids)`, `count(filters)`, `list_deleted()`
|
||||
|
||||
**Usage:**
|
||||
```python
|
||||
repo = BaseRepository("tasks", db)
|
||||
items = await repo.list(filters={"project_id": project_id})
|
||||
```
|
||||
|
||||
**Nullable fields gotcha:** When a form field should allow setting a value to empty/null, the field name MUST be in the `nullable_fields` set in `core/base_repository.py`. Otherwise `update()` silently skips null values. Check this set before adding new nullable form fields.
|
||||
|
||||
---
|
||||
|
||||
## Router Pattern - Follow Exactly
|
||||
|
||||
Every router follows this structure. Do not invent new patterns.
|
||||
|
||||
```python
|
||||
from fastapi import APIRouter, Request, Form, Depends
|
||||
from fastapi.responses import RedirectResponse
|
||||
from fastapi.templating import Jinja2Templates
|
||||
from core.base_repository import BaseRepository
|
||||
from core.database import get_db
|
||||
from core.sidebar import get_sidebar_data
|
||||
|
||||
router = APIRouter(prefix='/things', tags=['things'])
|
||||
templates = Jinja2Templates(directory='templates')
|
||||
|
||||
@router.get('/')
|
||||
async def list_things(request: Request, db=Depends(get_db)):
|
||||
repo = BaseRepository("things", db)
|
||||
items = await repo.list()
|
||||
sidebar = await get_sidebar_data(db)
|
||||
return templates.TemplateResponse('things.html', {
|
||||
'request': request, 'items': items, 'sidebar': sidebar
|
||||
})
|
||||
|
||||
@router.post('/create')
|
||||
async def create_thing(request: Request, db=Depends(get_db),
|
||||
title: str = Form(...), description: str = Form(None)):
|
||||
repo = BaseRepository("things", db)
|
||||
item = await repo.create({'title': title, 'description': description})
|
||||
return RedirectResponse(url=f'/things/{item["id"]}', status_code=303)
|
||||
```
|
||||
|
||||
**Every route MUST call `get_sidebar_data(db)` and pass `sidebar` to the template.** The sidebar navigation breaks without this.
|
||||
|
||||
---
|
||||
|
||||
## Adding a New Entity - Checklist
|
||||
|
||||
1. Create `routers/entity_name.py` using the pattern above
|
||||
2. Add import + `app.include_router(router)` in `main.py`
|
||||
3. Create templates in `templates/`: list, form, detail (all extend `base.html`)
|
||||
4. Add nav link in `templates/base.html` sidebar section
|
||||
5. Add entity config to `SEARCH_ENTITIES` dict in `routers/search.py`
|
||||
6. Add entity config to `TRASH_ENTITIES` dict in `routers/admin.py`
|
||||
7. Add any new nullable fields to `nullable_fields` in `core/base_repository.py`
|
||||
8. If entity has a new table, add seed fixture to `tests/conftest.py` and prefix mapping to `tests/registry.py`
|
||||
9. Test: visit the list page, create an item, edit it, delete it, verify it appears in trash and search
|
||||
|
||||
---
|
||||
|
||||
## Universal Column Conventions
|
||||
|
||||
Every table follows this structure. When creating new tables, match it exactly:
|
||||
|
||||
```sql
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
-- [foreign keys, nullable where optional]
|
||||
-- [content fields]
|
||||
tags TEXT[],
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
is_deleted BOOL NOT NULL DEFAULT false,
|
||||
deleted_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
-- searchable tables also get:
|
||||
-- search_vector TSVECTOR (maintained by trigger)
|
||||
-- CREATE INDEX idx_tablename_search ON tablename USING GIN(search_vector);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Enums / Values
|
||||
|
||||
**Task status:** `open | in_progress | blocked | done | cancelled`
|
||||
**Task priority:** `1=critical, 2=high, 3=normal, 4=low`
|
||||
**Project status:** `active | on_hold | completed | archived`
|
||||
**Process type:** `workflow | checklist`
|
||||
|
||||
---
|
||||
|
||||
## Known Traps
|
||||
|
||||
1. **`time_entries` has no `updated_at` column.** BaseRepository.soft_delete() will fail on this table. Use direct SQL instead. Same for restore.
|
||||
|
||||
2. **Junction tables use raw SQL, not BaseRepository.**
|
||||
```python
|
||||
await db.execute(text("INSERT INTO contact_meetings (contact_id, meeting_id, role) VALUES (:c, :m, :r) ON CONFLICT DO NOTHING"), {...})
|
||||
```
|
||||
|
||||
3. **Timer constraint:** Only one timer runs at a time. `get_running_task_id()` in `routers/tasks.py` queries `time_entries WHERE end_at IS NULL`. Starting a new timer auto-stops the running one.
|
||||
|
||||
4. **SSL cert path** on Nginx uses `kasm.invixiom.com-0001` (not `kasm.invixiom.com`).
|
||||
|
||||
5. **No pagination.** All list views load all rows. Fine at current data volume.
|
||||
|
||||
6. **No CSRF protection.** Single-user system.
|
||||
|
||||
7. **Schema SQL files may be stale.** Always verify against live DB, not the .sql files in project-docs.
|
||||
|
||||
8. **Test suite not fully green.** Async event loop fixes and seed data corrections are in progress.
|
||||
|
||||
---
|
||||
|
||||
## CSS Design Tokens
|
||||
|
||||
When adding styles, use these existing CSS custom properties. Do not hardcode colors.
|
||||
|
||||
```css
|
||||
/* Dark theme */
|
||||
--bg: #0D0E13; --surface: #14161F; --surface2: #1A1D28; --border: #252836;
|
||||
--text: #DDE1F5; --muted: #5A6080;
|
||||
--accent: #4F6EF7; --accent-soft: rgba(79,110,247,.12);
|
||||
--green: #22C98A; --amber: #F5A623; --red: #F05252; --purple: #9B7FF5;
|
||||
|
||||
/* Light theme */
|
||||
--bg: #F0F2F8; --surface: #FFFFFF; --surface2: #F7F8FC; --border: #E3E6F0;
|
||||
--text: #171926; --muted: #8892B0;
|
||||
--accent: #4F6EF7; --accent-soft: rgba(79,110,247,.10);
|
||||
--green: #10B981; --amber: #F59E0B; --red: #DC2626;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Docker / Infrastructure
|
||||
|
||||
### Containers
|
||||
| Container | Image | Port | Purpose |
|
||||
|-----------|-------|------|---------|
|
||||
| lifeos-db | postgres:16-alpine | 5432 (internal) | PostgreSQL: lifeos_dev + lifeos_prod + lifeos_test |
|
||||
| lifeos-dev | lifeos-app | 8003 | Dev application (hot reload) |
|
||||
| lifeos-prod | lifeos-app | 8002 | Prod application (NOT YET DEPLOYED) |
|
||||
|
||||
### How the Dev Container Runs
|
||||
```bash
|
||||
docker run -d \
|
||||
--name lifeos-dev \
|
||||
--network lifeos_network \
|
||||
--restart unless-stopped \
|
||||
--env-file .env \
|
||||
-p 8003:8003 \
|
||||
-v /opt/lifeos/dev/files:/opt/lifeos/files/dev \
|
||||
-v /opt/lifeos/dev:/app \
|
||||
lifeos-app \
|
||||
uvicorn main:app --host 0.0.0.0 --port 8003 --workers 1 --reload
|
||||
```
|
||||
|
||||
### Nginx
|
||||
Host-level (not containerized). Config at `/etc/nginx/sites-available/invixiom`.
|
||||
- `lifeos-dev.invixiom.com` -> `localhost:8003`
|
||||
- `lifeos.invixiom.com` -> `localhost:8002` (pending)
|
||||
- SSL cert at `/etc/letsencrypt/live/kasm.invixiom.com-0001/`
|
||||
|
||||
### Docker Network
|
||||
`lifeos_network` (172.21.0.0/16) - bridges lifeos-db, lifeos-dev, lifeos-prod
|
||||
|
||||
---
|
||||
|
||||
## Test Suite
|
||||
|
||||
Tests use dynamic introspection. They discover routes from the live app at runtime. No hardcoded routes.
|
||||
|
||||
```bash
|
||||
docker exec lifeos-dev bash /app/tests/run_tests.sh # Full suite
|
||||
docker exec lifeos-dev bash /app/tests/run_tests.sh report # Route introspection dump
|
||||
docker exec lifeos-dev bash /app/tests/run_tests.sh smoke # All GET endpoints
|
||||
docker exec lifeos-dev bash /app/tests/run_tests.sh crud # All POST create/edit/delete
|
||||
docker exec lifeos-dev bash /app/tests/run_tests.sh logic # Business logic
|
||||
docker exec lifeos-dev bash /app/tests/run_tests.sh fast # Smoke, stop on first fail
|
||||
docker exec lifeos-dev bash /app/tests/run_tests.sh -k "timer" # Keyword filter
|
||||
```
|
||||
|
||||
**After schema changes, reset the test DB:**
|
||||
```bash
|
||||
docker exec lifeos-db psql -U postgres -c "DROP DATABASE IF EXISTS lifeos_test;"
|
||||
docker exec lifeos-db psql -U postgres -c "CREATE DATABASE lifeos_test;"
|
||||
docker exec lifeos-db pg_dump -U postgres -d lifeos_dev --schema-only -f /tmp/s.sql
|
||||
docker exec lifeos-db psql -U postgres -d lifeos_test -f /tmp/s.sql -q
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Current Build State (as of 2026-03-01)
|
||||
|
||||
**Phase 1, Tier 3 in progress.** 2 of 6 Tier 3 features complete.
|
||||
|
||||
### What's Built
|
||||
18 routers, 42 templates. Core CRUD for: domains, areas, projects, tasks, notes, links, focus, capture, contacts, search, admin/trash, lists, files, meetings, decisions, weblinks, appointments, time_tracking.
|
||||
|
||||
Working features: collapsible sidebar nav tree, task filtering/sorting/priority/status/context, daily focus, markdown notes with preview, file uploads with inline preview, global search (Cmd/K) via tsvector, admin trash with restore, time tracking with topbar timer pill, timer play/stop on task rows and detail pages.
|
||||
|
||||
### What's NOT Built Yet
|
||||
|
||||
**Tier 3 remaining (4 features, build in this order):**
|
||||
1. **Processes / Process Runs** - Most complex. 4 tables: processes, process_steps, process_runs, process_run_steps. Template CRUD, run instantiation (copies steps as immutable snapshot), step completion, task generation (all_at_once vs step_by_step).
|
||||
2. **Calendar view** - Unified `/calendar`: appointments (start_at) + meetings (meeting_date) + tasks (due_date). No new tables. Read-only.
|
||||
3. **Time budgets** - Simple CRUD: domain_id + weekly_hours + effective_from. Dashboard overcommitment warnings.
|
||||
4. **Eisenhower matrix** - Derived 2x2 grid. Priority 1-2 = Important, 3-4 = Not. Due <=7d = Urgent. Clickable quadrants.
|
||||
|
||||
**Tier 4 (later):** Releases/milestones, dependencies (DAG with cycle detection), task templates, note wiki-linking, note folders, bulk actions, CSV export, drag-to-reorder, reminders, dashboard metrics.
|
||||
|
||||
**Phase 2:** DAG visualization, MCP/AI gateway (dual: MCP + OpenAI function calling), note version history.
|
||||
**Phase 3:** Mobile improvements, authentication, browser extension.
|
||||
|
||||
---
|
||||
|
||||
## Conversation History
|
||||
|
||||
| Session | What Was Built |
|
||||
|---------|----------------|
|
||||
| Pre-build | Architecture doc (50 tables, 3-phase plan), server config, schema design |
|
||||
| Convo 1 | Foundation: 9 routers (domains, areas, projects, tasks, notes, links, focus, capture, contacts), base templates, sidebar, dashboard |
|
||||
| Convo 2 | 7 more routers: search, admin/trash, lists, files, meetings, decisions, weblinks |
|
||||
| Convo 3 | Tier 3 start: appointments CRUD, time tracking with topbar timer pill |
|
||||
| Convo 4 | Timer buttons on task rows and detail pages (completes time tracking UX) |
|
||||
| Test1 | Dynamic introspection-based test suite (11 files, 121 routes discovered) |
|
||||
| Test2 | Test suite debugging: async event loop fixes, seed data corrections (in progress) |
|
||||
|
||||
---
|
||||
|
||||
## Database Schema Overview (48 tables in dev)
|
||||
|
||||
### Core Hierarchy
|
||||
domains, areas, projects, tasks
|
||||
|
||||
### Content
|
||||
notes, note_folders, lists, list_items, links, files, weblinks, weblink_folders
|
||||
|
||||
### CRM & Meetings
|
||||
contacts, appointments, meetings, decisions
|
||||
|
||||
### Time Management
|
||||
time_entries, time_blocks, time_budgets, daily_focus
|
||||
|
||||
### Processes (tables exist, CRUD not built yet)
|
||||
processes, process_steps, process_runs, process_run_steps
|
||||
|
||||
### System
|
||||
capture, context_types, reminders, dependencies, releases, milestones, task_templates, task_template_items
|
||||
|
||||
### Junction Tables (17)
|
||||
note_projects, note_links, file_mappings, release_projects, release_domains, contact_tasks, contact_projects, contact_lists, contact_list_items, contact_appointments, contact_meetings, decision_projects, decision_contacts, meeting_tasks, process_run_tasks, folder_weblinks, note_version_history
|
||||
|
||||
---
|
||||
|
||||
## Reference Documents
|
||||
|
||||
These files are in the `project-docs/` folder alongside this CLAUDE.md. Consult them when you need deeper context:
|
||||
|
||||
| File | What It Contains |
|
||||
|------|------------------|
|
||||
| `lifeos-development-status-convo4.md` | **App source of truth.** Complete inventory of routers, templates, deploy patterns, what's remaining. |
|
||||
| `lifeos-development-status-test1.md` | **Test source of truth.** Test architecture, seed data, introspection details. |
|
||||
| `lifeos-conversation-context-convo4.md` | Quick context card for app development. |
|
||||
| `lifeos-conversation-context-convo-test1.md` | Quick context card for test development. |
|
||||
| `lifeos-architecture.docx` | Full system spec. 50 tables, all subsystems, UI patterns, build plan. |
|
||||
| `life-os-server-config.docx` | Server infrastructure: containers, ports, networks, Nginx, SSL details. |
|
||||
| `lifeos_r1_full_schema.sql` | Intended R1 schema (may not match actual DB - always verify against live). |
|
||||
| `lifeos_schema_r1.sql` | Schema reference (same caveat). |
|
||||
| `lifeos_r0_to_r1_migration.sql` | Migration script from old Supabase schema. |
|
||||
| `lifeos-setup.sh` | Repeatable server infrastructure setup script. |
|
||||
| `setup_dev_database.sh` | Dev database setup. |
|
||||
| `setup_prod_database.sh` | Prod database setup. |
|
||||
| `lifeos-database-backup.md` | Backup and restore commands. |
|
||||
| `lifeos-v2-migration-plan.docx` | V2 migration planning. |
|
||||
| `_liefos-dev-test_results1.txt` | First test deploy output (introspection verification). |
|
||||
|
||||
---
|
||||
|
||||
## Design Principles
|
||||
|
||||
- **KISS.** Simple wins. Complexity must earn its place.
|
||||
- **Logical deletes everywhere.** No data permanently destroyed without admin confirmation.
|
||||
- **Generic over specific.** Shared base code. Config-driven where possible.
|
||||
- **Consistent patterns.** Every list, form, detail view follows identical conventions.
|
||||
- **Search is first-class.** Every new entity gets added to global search.
|
||||
- **Context travels with navigation.** Drill-down pre-fills context into create forms.
|
||||
- **Single source of truth.** One system for everything.
|
||||
Reference in New Issue
Block a user