feat: enhanced capture queue with multi-type conversion, and bottom menu bar for cell phones

This commit is contained in:
2026-03-01 21:48:15 +00:00
parent a21e00d0e0
commit dbd40485ba
17 changed files with 16450 additions and 0 deletions

View File

@@ -0,0 +1,263 @@
-- =============================================================================
-- Life OS - R0 to R1 Data Migration (FIXED)
-- Source: lifeos_prod (R0 schema - actual)
-- Target: lifeos_dev (R1 schema)
--
-- PREREQUISITE: R1 schema must already be applied to lifeos_dev
-- RUN FROM: lifeos_dev database as postgres user
-- =============================================================================
CREATE EXTENSION IF NOT EXISTS dblink;
-- =============================================================================
-- 1. DOMAINS
-- R0: id, name, color, created_at
-- R1: + description, icon, sort_order, is_deleted, deleted_at, updated_at, search_vector
-- =============================================================================
INSERT INTO domains (id, name, color, sort_order, is_deleted, created_at, updated_at)
SELECT id, name, color,
(ROW_NUMBER() OVER (ORDER BY created_at))::INTEGER * 10,
false, created_at, created_at
FROM dblink('dbname=lifeos_prod', '
SELECT id, name, color, created_at FROM domains
') AS r0(id UUID, name TEXT, color TEXT, created_at TIMESTAMPTZ);
-- =============================================================================
-- 2. AREAS
-- R0: id, domain_id, name, description, status, created_at
-- R1: + icon, color, sort_order, is_deleted, deleted_at, updated_at, search_vector
-- =============================================================================
INSERT INTO areas (id, domain_id, name, description, status, sort_order, is_deleted, created_at, updated_at)
SELECT id, domain_id, name, description, COALESCE(status, 'active'),
(ROW_NUMBER() OVER (PARTITION BY domain_id ORDER BY created_at))::INTEGER * 10,
false, created_at, created_at
FROM dblink('dbname=lifeos_prod', '
SELECT id, domain_id, name, description, status, created_at FROM areas
') AS r0(id UUID, domain_id UUID, name TEXT, description TEXT, status TEXT, created_at TIMESTAMPTZ);
-- =============================================================================
-- 3. PROJECTS
-- R0: id, domain_id, name, description, status, priority, start_date,
-- target_date, completed_at, tags, created_at, updated_at, area_id
-- R1: + color, sort_order, is_deleted, deleted_at, search_vector
-- =============================================================================
INSERT INTO projects (id, domain_id, area_id, name, description, status, priority,
start_date, target_date, completed_at, tags, sort_order, is_deleted, created_at, updated_at)
SELECT id, domain_id, area_id, name, description,
COALESCE(status, 'active'), COALESCE(priority, 3),
start_date, target_date, completed_at, tags,
(ROW_NUMBER() OVER (PARTITION BY domain_id ORDER BY created_at))::INTEGER * 10,
false, created_at, COALESCE(updated_at, created_at)
FROM dblink('dbname=lifeos_prod', '
SELECT id, domain_id, area_id, name, description, status, priority,
start_date, target_date, completed_at, tags, created_at, updated_at
FROM projects
') AS r0(
id UUID, domain_id UUID, area_id UUID, name TEXT, description TEXT,
status TEXT, priority INTEGER, start_date DATE, target_date DATE,
completed_at TIMESTAMPTZ, tags TEXT[], created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ
);
-- =============================================================================
-- 4. TASKS
-- R0: id, domain_id, project_id, parent_id, title, description, priority,
-- status, due_date, deadline, recurrence, tags, context, is_custom_context,
-- created_at, updated_at, completed_at
-- R1: + area_id, release_id, estimated_minutes, energy_required,
-- waiting_for_contact_id, waiting_since, import_batch_id,
-- sort_order, is_deleted, deleted_at, search_vector
-- NOTE: R0 has no area_id on tasks. Left NULL in R1.
-- =============================================================================
INSERT INTO tasks (id, domain_id, project_id, parent_id, title, description,
priority, status, due_date, deadline, recurrence, tags, context,
is_custom_context, sort_order, is_deleted, created_at, updated_at, completed_at)
SELECT id, domain_id, project_id, parent_id, title, description,
COALESCE(priority, 3), COALESCE(status, 'open'),
due_date, deadline, recurrence, tags, context,
COALESCE(is_custom_context, false),
(ROW_NUMBER() OVER (PARTITION BY project_id ORDER BY created_at))::INTEGER * 10,
false, created_at, COALESCE(updated_at, created_at), completed_at
FROM dblink('dbname=lifeos_prod', '
SELECT id, domain_id, project_id, parent_id, title, description,
priority, status, due_date, deadline, recurrence, tags, context,
is_custom_context, created_at, updated_at, completed_at
FROM tasks
') AS r0(
id UUID, domain_id UUID, project_id UUID, parent_id UUID,
title TEXT, description TEXT, priority INTEGER, status TEXT,
due_date DATE, deadline TIMESTAMPTZ, recurrence TEXT, tags TEXT[],
context TEXT, is_custom_context BOOLEAN,
created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ, completed_at TIMESTAMPTZ
);
-- =============================================================================
-- 5. NOTES
-- R0: id, domain_id, project_id, task_id, title, body, tags,
-- created_at, updated_at, content_format (default 'markdown')
-- R1: + folder_id, meeting_id, is_meeting_note, sort_order,
-- is_deleted, deleted_at, search_vector
-- Transform: content_format 'markdown' -> 'rich'
-- NOTE: R0 task_id dropped (no equivalent in R1 notes).
-- =============================================================================
INSERT INTO notes (id, domain_id, project_id, title, body, content_format, tags,
is_meeting_note, sort_order, is_deleted, created_at, updated_at)
SELECT id, domain_id, project_id,
CASE WHEN title IS NULL OR title = '' THEN 'Untitled Note' ELSE title END,
body,
CASE WHEN content_format = 'markdown' THEN 'rich' ELSE COALESCE(content_format, 'rich') END,
tags, false,
(ROW_NUMBER() OVER (ORDER BY created_at))::INTEGER * 10,
false, created_at, COALESCE(updated_at, created_at)
FROM dblink('dbname=lifeos_prod', '
SELECT id, domain_id, project_id, title, body, content_format, tags,
created_at, updated_at
FROM notes
') AS r0(
id UUID, domain_id UUID, project_id UUID, title TEXT, body TEXT,
content_format TEXT, tags TEXT[],
created_at TIMESTAMPTZ, updated_at TIMESTAMPTZ
);
-- =============================================================================
-- 6. LINKS
-- R0: id, domain_id, project_id, task_id, label, url, description, created_at
-- R1: + area_id, tags, sort_order, is_deleted, deleted_at, updated_at, search_vector
-- NOTE: R0 task_id dropped (no equivalent in R1 links).
-- =============================================================================
INSERT INTO links (id, domain_id, project_id, label, url, description,
sort_order, is_deleted, created_at, updated_at)
SELECT id, domain_id, project_id, label, url, description,
(ROW_NUMBER() OVER (ORDER BY created_at))::INTEGER * 10,
false, created_at, created_at
FROM dblink('dbname=lifeos_prod', '
SELECT id, domain_id, project_id, label, url, description, created_at
FROM links
') AS r0(
id UUID, domain_id UUID, project_id UUID,
label TEXT, url TEXT, description TEXT, created_at TIMESTAMPTZ
);
-- =============================================================================
-- 7. DAILY FOCUS
-- R0: id, focus_date, task_id, slot, completed, note, created_at
-- R1: + sort_order, is_deleted, deleted_at
-- =============================================================================
INSERT INTO daily_focus (id, focus_date, task_id, slot, completed, note,
sort_order, is_deleted, created_at)
SELECT id, focus_date, task_id, slot, COALESCE(completed, false), note,
COALESCE(slot, (ROW_NUMBER() OVER (PARTITION BY focus_date ORDER BY created_at))::INTEGER) * 10,
false, created_at
FROM dblink('dbname=lifeos_prod', '
SELECT id, focus_date, task_id, slot, completed, note, created_at
FROM daily_focus
') AS r0(
id UUID, focus_date DATE, task_id UUID, slot INTEGER,
completed BOOLEAN, note TEXT, created_at TIMESTAMPTZ
);
-- =============================================================================
-- 8. CAPTURE
-- R0: id, raw_text, processed, task_id, created_at
-- R1: + converted_to_type, converted_to_id, area_id, project_id, list_id,
-- import_batch_id, sort_order, is_deleted, deleted_at
-- Map: R0 task_id -> R1 converted_to_type='task', converted_to_id=task_id
-- =============================================================================
INSERT INTO capture (id, raw_text, processed, converted_to_type, converted_to_id,
sort_order, is_deleted, created_at)
SELECT id, raw_text, COALESCE(processed, false),
CASE WHEN task_id IS NOT NULL THEN 'task' ELSE NULL END,
task_id,
(ROW_NUMBER() OVER (ORDER BY created_at))::INTEGER * 10,
false, created_at
FROM dblink('dbname=lifeos_prod', '
SELECT id, raw_text, processed, task_id, created_at FROM capture
') AS r0(
id UUID, raw_text TEXT, processed BOOLEAN, task_id UUID, created_at TIMESTAMPTZ
);
-- =============================================================================
-- 9. CONTEXT TYPES
-- R0: id (SERIAL), name, description, is_system
-- R1: id (SERIAL), value, label, description, is_system, sort_order, is_deleted
-- Map: R0.name -> R1.value, generate label from name via INITCAP
-- =============================================================================
DELETE FROM context_types;
INSERT INTO context_types (id, value, label, description, is_system, sort_order, is_deleted)
SELECT id, name,
INITCAP(REPLACE(name, '_', ' ')),
description, COALESCE(is_system, true),
id * 10,
false
FROM dblink('dbname=lifeos_prod', '
SELECT id, name, description, is_system FROM context_types
') AS r0(id INTEGER, name TEXT, description TEXT, is_system BOOLEAN);
SELECT setval('context_types_id_seq', GREATEST((SELECT MAX(id) FROM context_types), 1));
-- =============================================================================
-- VERIFICATION
-- =============================================================================
DO $$
DECLARE
r0_domains INTEGER;
r0_areas INTEGER;
r0_projects INTEGER;
r0_tasks INTEGER;
r0_notes INTEGER;
r0_links INTEGER;
r0_daily_focus INTEGER;
r0_capture INTEGER;
r0_context INTEGER;
r1_domains INTEGER;
r1_areas INTEGER;
r1_projects INTEGER;
r1_tasks INTEGER;
r1_notes INTEGER;
r1_links INTEGER;
r1_daily_focus INTEGER;
r1_capture INTEGER;
r1_context INTEGER;
BEGIN
SELECT count INTO r0_domains FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM domains') AS t(count INTEGER);
SELECT count INTO r0_areas FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM areas') AS t(count INTEGER);
SELECT count INTO r0_projects FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM projects') AS t(count INTEGER);
SELECT count INTO r0_tasks FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM tasks') AS t(count INTEGER);
SELECT count INTO r0_notes FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM notes') AS t(count INTEGER);
SELECT count INTO r0_links FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM links') AS t(count INTEGER);
SELECT count INTO r0_daily_focus FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM daily_focus') AS t(count INTEGER);
SELECT count INTO r0_capture FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM capture') AS t(count INTEGER);
SELECT count INTO r0_context FROM dblink('dbname=lifeos_prod', 'SELECT count(*) FROM context_types') AS t(count INTEGER);
SELECT count(*) INTO r1_domains FROM domains;
SELECT count(*) INTO r1_areas FROM areas;
SELECT count(*) INTO r1_projects FROM projects;
SELECT count(*) INTO r1_tasks FROM tasks;
SELECT count(*) INTO r1_notes FROM notes;
SELECT count(*) INTO r1_links FROM links;
SELECT count(*) INTO r1_daily_focus FROM daily_focus;
SELECT count(*) INTO r1_capture FROM capture;
SELECT count(*) INTO r1_context FROM context_types;
RAISE NOTICE '=== MIGRATION VERIFICATION ===';
RAISE NOTICE 'domains: R0=% R1=% %', r0_domains, r1_domains, CASE WHEN r0_domains = r1_domains THEN 'OK' ELSE 'MISMATCH' END;
RAISE NOTICE 'areas: R0=% R1=% %', r0_areas, r1_areas, CASE WHEN r0_areas = r1_areas THEN 'OK' ELSE 'MISMATCH' END;
RAISE NOTICE 'projects: R0=% R1=% %', r0_projects, r1_projects, CASE WHEN r0_projects = r1_projects THEN 'OK' ELSE 'MISMATCH' END;
RAISE NOTICE 'tasks: R0=% R1=% %', r0_tasks, r1_tasks, CASE WHEN r0_tasks = r1_tasks THEN 'OK' ELSE 'MISMATCH' END;
RAISE NOTICE 'notes: R0=% R1=% %', r0_notes, r1_notes, CASE WHEN r0_notes = r1_notes THEN 'OK' ELSE 'MISMATCH' END;
RAISE NOTICE 'links: R0=% R1=% %', r0_links, r1_links, CASE WHEN r0_links = r1_links THEN 'OK' ELSE 'MISMATCH' END;
RAISE NOTICE 'daily_focus: R0=% R1=% %', r0_daily_focus, r1_daily_focus, CASE WHEN r0_daily_focus = r1_daily_focus THEN 'OK' ELSE 'MISMATCH' END;
RAISE NOTICE 'capture: R0=% R1=% %', r0_capture, r1_capture, CASE WHEN r0_capture = r1_capture THEN 'OK' ELSE 'MISMATCH' END;
RAISE NOTICE 'context_types:R0=% R1=% %', r0_context, r1_context, CASE WHEN r0_context = r1_context THEN 'OK' ELSE 'MISMATCH' END;
RAISE NOTICE '=== END VERIFICATION ===';
END $$;