From 318e96d606dcfb7011560fc6c3bd2c92469d65c3 Mon Sep 17 00:00:00 2001 From: Krusty Date: Fri, 6 Feb 2026 22:40:20 -0800 Subject: [PATCH 001/201] Add GitHub Actions workflow for Convex deployment --- .github/workflows/deploy-convex.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/deploy-convex.yml diff --git a/.github/workflows/deploy-convex.yml b/.github/workflows/deploy-convex.yml new file mode 100644 index 0000000..aedb53a --- /dev/null +++ b/.github/workflows/deploy-convex.yml @@ -0,0 +1,27 @@ +name: Deploy Convex + +on: + push: + branches: [main] + paths: + - 'convex/**' + - '.github/workflows/deploy-convex.yml' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - name: Install dependencies + run: bun install + + - name: Deploy to Convex + run: bunx convex deploy + env: + CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }} From 5547524acd8cf7c4234070f0c9b7eac0ea69321f Mon Sep 17 00:00:00 2001 From: Krusty Date: Fri, 6 Feb 2026 22:40:49 -0800 Subject: [PATCH 002/201] fix: smaller checkboxes/text, cleaner input placeholder - Reduced checkbox size from w-7 to w-5 - Reduced item text from text-sm to text-xs - Removed 'Press Enter' hint, simplified to 'Add item...' placeholder - Checked items already sort below unchecked (was already implemented) --- .github/workflows/deploy-convex.yml | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 .github/workflows/deploy-convex.yml diff --git a/.github/workflows/deploy-convex.yml b/.github/workflows/deploy-convex.yml deleted file mode 100644 index aedb53a..0000000 --- a/.github/workflows/deploy-convex.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Deploy Convex - -on: - push: - branches: [main] - paths: - - 'convex/**' - - '.github/workflows/deploy-convex.yml' - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Install dependencies - run: bun install - - - name: Deploy to Convex - run: bunx convex deploy - env: - CONVEX_DEPLOY_KEY: ${{ secrets.CONVEX_DEPLOY_KEY }} From 2c75e0d9c26bd192057895ebf7388f26c70ccf32 Mon Sep 17 00:00:00 2001 From: Brian Richter Date: Fri, 6 Feb 2026 22:56:33 -0800 Subject: [PATCH 003/201] Add GitHub Actions workflow for Convex deployment --- .github/workflows/deploy-convex.yaml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/deploy-convex.yaml diff --git a/.github/workflows/deploy-convex.yaml b/.github/workflows/deploy-convex.yaml new file mode 100644 index 0000000..0603891 --- /dev/null +++ b/.github/workflows/deploy-convex.yaml @@ -0,0 +1,27 @@ +name: Deploy Convex + +on: + push: + branches: [main] + paths: + - 'convex/**' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Deploy to Convex + env: + CONVEX_SELF_HOSTED_URL: ${{ secrets.CONVEX_SELF_HOSTED_URL }} + CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ secrets.CONVEX_SELF_HOSTED_ADMIN_KEY }} + run: npx convex deploy \ No newline at end of file From c085c45a6e152ec7bba5df947c4711eeea817a6e Mon Sep 17 00:00:00 2001 From: Brian Richter Date: Fri, 6 Feb 2026 22:58:32 -0800 Subject: [PATCH 004/201] Initialize README file for Poo App --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..2867410 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Poo App \ No newline at end of file From 4782166ae9da26cd6936aed9e9857f9b4ef666fd Mon Sep 17 00:00:00 2001 From: Krusty Date: Fri, 6 Feb 2026 23:03:13 -0800 Subject: [PATCH 005/201] docs: add schema file header comment --- .github/workflows/deploy-convex.yaml | 27 --------------------------- .gitignore | 1 + convex/schema.ts | 6 ++++++ 3 files changed, 7 insertions(+), 27 deletions(-) delete mode 100644 .github/workflows/deploy-convex.yaml diff --git a/.github/workflows/deploy-convex.yaml b/.github/workflows/deploy-convex.yaml deleted file mode 100644 index 0603891..0000000 --- a/.github/workflows/deploy-convex.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: Deploy Convex - -on: - push: - branches: [main] - paths: - - 'convex/**' - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Install dependencies - run: npm ci - - - name: Deploy to Convex - env: - CONVEX_SELF_HOSTED_URL: ${{ secrets.CONVEX_SELF_HOSTED_URL }} - CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ secrets.CONVEX_SELF_HOSTED_ADMIN_KEY }} - run: npx convex deploy \ No newline at end of file diff --git a/.gitignore b/.gitignore index d21d049..fc11f14 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ dist/ # OS .DS_Store +.github/workflows/ diff --git a/convex/schema.ts b/convex/schema.ts index 7301749..9191c9f 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -1,3 +1,9 @@ +/** + * Poo App Database Schema + * + * Core tables for the collaborative list-sharing app with DID-based identity. + */ + import { defineSchema, defineTable } from "convex/server"; import { v } from "convex/values"; From 66fffdf5329d25110fb0c6ff6a246a8c9359bae1 Mon Sep 17 00:00:00 2001 From: Brian Richter Date: Fri, 6 Feb 2026 23:14:21 -0800 Subject: [PATCH 006/201] Add Convex deploy workflow --- .github/workflows/deploy-convex.yaml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .github/workflows/deploy-convex.yaml diff --git a/.github/workflows/deploy-convex.yaml b/.github/workflows/deploy-convex.yaml new file mode 100644 index 0000000..c7801a9 --- /dev/null +++ b/.github/workflows/deploy-convex.yaml @@ -0,0 +1,28 @@ +name: Deploy Convex + +on: + push: + branches: [main] + paths: + - 'convex/**' + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Deploy to Convex + env: + CONVEX_SELF_HOSTED_URL: ${{ secrets.CONVEX_SELF_HOSTED_URL }} + CONVEX_SELF_HOSTED_ADMIN_KEY: ${{ secrets.CONVEX_SELF_HOSTED_ADMIN_KEY }} + run: npx convex deploy + \ No newline at end of file From 8043045f17dc2cc59ff93718e28b306c955f6c32 Mon Sep 17 00:00:00 2001 From: Brian Richter Date: Fri, 6 Feb 2026 23:15:03 -0800 Subject: [PATCH 007/201] chore: remove GitHub workflows directory from .gitignore --- .gitignore | 1 - convex/init.txt | 0 2 files changed, 1 deletion(-) create mode 100644 convex/init.txt diff --git a/.gitignore b/.gitignore index fc11f14..d21d049 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,3 @@ dist/ # OS .DS_Store -.github/workflows/ diff --git a/convex/init.txt b/convex/init.txt new file mode 100644 index 0000000..e69de29 From 02ff530595f3e5cde560b8a61d3edcadfcbb3cf7 Mon Sep 17 00:00:00 2001 From: Krusty Date: Fri, 6 Feb 2026 23:33:01 -0800 Subject: [PATCH 008/201] docs: expand schema header comment --- convex/schema.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/convex/schema.ts b/convex/schema.ts index 9191c9f..7389763 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -1,7 +1,8 @@ /** * Poo App Database Schema - * + * * Core tables for the collaborative list-sharing app with DID-based identity. + * Uses Convex for real-time sync and offline support. */ import { defineSchema, defineTable } from "convex/server"; From 2a92eb42c7a9b4ebf3c2f662b6a7b295b5338771 Mon Sep 17 00:00:00 2001 From: Brian Richter Date: Fri, 6 Feb 2026 23:47:50 -0800 Subject: [PATCH 009/201] feat: add workflow_dispatch trigger to Convex deployment workflow --- .github/workflows/deploy-convex.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/deploy-convex.yaml b/.github/workflows/deploy-convex.yaml index c7801a9..c9c6381 100644 --- a/.github/workflows/deploy-convex.yaml +++ b/.github/workflows/deploy-convex.yaml @@ -5,6 +5,7 @@ on: branches: [main] paths: - 'convex/**' + workflow_dispatch: jobs: deploy: From 41fc9ec983a1b1dd1b9ff53ea71b1050ab3703aa Mon Sep 17 00:00:00 2001 From: Krusty Date: Sat, 7 Feb 2026 00:04:35 -0800 Subject: [PATCH 010/201] feat: batch 1 - six features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. Fix Profile DID display inconsistency - ProfileBadge now uses useCurrentUser() instead of useAuth() - Shows canonical DID (did:webvh) consistently 2. Move new item input to top of list - AddItemInput now appears above items, not below - Better UX for adding items 3. Link PRs/URLs to items - Added url field to items schema - Shows 🔗 indicator when item has a link 4. Notes/Details for items - Added description field to items schema - Shows 📝 indicator when item has notes 5. Due dates for items - Added dueDate field to items schema - Shows date badge with color coding (overdue = red) 6. Recurring items - Added recurrence field to items schema - Supports daily/weekly/monthly frequency - Shows 🔁 indicator for recurring items New components: - ItemDetailsModal: Edit item details (notes, due date, URL, recurrence) Schema changes: - items table: added description, dueDate, url, recurrence fields - New updateItem mutation for editing item details --- convex/items.ts | 68 +++++++++ convex/schema.ts | 9 ++ src/components/ItemDetailsModal.tsx | 217 ++++++++++++++++++++++++++++ src/components/ListItem.tsx | 85 +++++++++-- src/components/ProfileBadge.tsx | 5 +- src/hooks/useOptimisticItems.tsx | 5 + src/lib/offline.ts | 9 ++ src/pages/ListView.tsx | 14 +- 8 files changed, 389 insertions(+), 23 deletions(-) create mode 100644 src/components/ItemDetailsModal.tsx diff --git a/convex/items.ts b/convex/items.ts index 28f2528..366cd27 100644 --- a/convex/items.ts +++ b/convex/items.ts @@ -58,6 +58,15 @@ export const addItem = mutation({ createdByDid: v.string(), legacyDid: v.optional(v.string()), createdAt: v.number(), + // Optional enhanced fields + description: v.optional(v.string()), + dueDate: v.optional(v.number()), + url: v.optional(v.string()), + recurrence: v.optional(v.object({ + frequency: v.union(v.literal("daily"), v.literal("weekly"), v.literal("monthly")), + interval: v.optional(v.number()), + nextDue: v.optional(v.number()), + })), }, handler: async (ctx, args) => { // Verify the list exists @@ -98,10 +107,69 @@ export const addItem = mutation({ checkedAt: undefined, order: maxOrder + 1, updatedAt: now, + // Enhanced fields + description: args.description, + dueDate: args.dueDate, + url: args.url, + recurrence: args.recurrence, }); }, }); +/** + * Update an item's details (name, description, due date, url, recurrence). + * Supports legacy DID for migrated users. + */ +export const updateItem = mutation({ + args: { + itemId: v.id("items"), + userDid: v.string(), + legacyDid: v.optional(v.string()), + // Fields that can be updated + name: v.optional(v.string()), + description: v.optional(v.string()), + dueDate: v.optional(v.number()), + url: v.optional(v.string()), + recurrence: v.optional(v.object({ + frequency: v.union(v.literal("daily"), v.literal("weekly"), v.literal("monthly")), + interval: v.optional(v.number()), + nextDue: v.optional(v.number()), + })), + clearDueDate: v.optional(v.boolean()), + clearRecurrence: v.optional(v.boolean()), + clearUrl: v.optional(v.boolean()), + }, + handler: async (ctx, args) => { + const item = await ctx.db.get(args.itemId); + if (!item) { + throw new Error("Item not found"); + } + + const canEdit = await canUserEditList(ctx, item.listId, args.userDid, args.legacyDid); + if (!canEdit) { + throw new Error("Not authorized to update this item"); + } + + const updates: Record = { + updatedAt: Date.now(), + }; + + if (args.name !== undefined) updates.name = args.name; + if (args.description !== undefined) updates.description = args.description; + if (args.dueDate !== undefined) updates.dueDate = args.dueDate; + if (args.url !== undefined) updates.url = args.url; + if (args.recurrence !== undefined) updates.recurrence = args.recurrence; + + // Clear fields if requested + if (args.clearDueDate) updates.dueDate = undefined; + if (args.clearRecurrence) updates.recurrence = undefined; + if (args.clearUrl) updates.url = undefined; + + await ctx.db.patch(args.itemId, updates); + return args.itemId; + }, +}); + /** * Check (mark as complete) an item. * Supports legacy DID for migrated users. diff --git a/convex/schema.ts b/convex/schema.ts index 7389763..7b28805 100644 --- a/convex/schema.ts +++ b/convex/schema.ts @@ -103,6 +103,15 @@ export default defineSchema({ checkedAt: v.optional(v.number()), order: v.optional(v.number()), // Position in list (lower = higher in list) updatedAt: v.optional(v.number()), // Timestamp of last update (Phase 5.8 conflict resolution) + // New fields for enhanced items + description: v.optional(v.string()), // Notes/details for the item + dueDate: v.optional(v.number()), // Due date timestamp + url: v.optional(v.string()), // Link to PR, URL, or reference + recurrence: v.optional(v.object({ + frequency: v.union(v.literal("daily"), v.literal("weekly"), v.literal("monthly")), + interval: v.optional(v.number()), // Every N days/weeks/months (default 1) + nextDue: v.optional(v.number()), // Next occurrence timestamp + })), }).index("by_list", ["listId"]), // Invites table - for sharing lists with partners diff --git a/src/components/ItemDetailsModal.tsx b/src/components/ItemDetailsModal.tsx new file mode 100644 index 0000000..7df42d8 --- /dev/null +++ b/src/components/ItemDetailsModal.tsx @@ -0,0 +1,217 @@ +/** + * Modal for viewing and editing item details. + * Supports notes, due dates, URLs/links, and recurrence settings. + */ + +import { useState, useEffect } from "react"; +import { useMutation } from "convex/react"; +import { api } from "../../convex/_generated/api"; +import type { Doc } from "../../convex/_generated/dataModel"; +import { useSettings } from "../hooks/useSettings"; + +interface ItemDetailsModalProps { + item: Doc<"items">; + userDid: string; + legacyDid?: string; + canEdit: boolean; + onClose: () => void; +} + +type RecurrenceFrequency = "daily" | "weekly" | "monthly"; + +export function ItemDetailsModal({ + item, + userDid, + legacyDid, + canEdit, + onClose, +}: ItemDetailsModalProps) { + const { haptic } = useSettings(); + const updateItem = useMutation(api.items.updateItem); + + const [name, setName] = useState(item.name); + const [description, setDescription] = useState(item.description ?? ""); + const [url, setUrl] = useState(item.url ?? ""); + const [dueDate, setDueDate] = useState( + item.dueDate ? new Date(item.dueDate).toISOString().split("T")[0] : "" + ); + const [hasRecurrence, setHasRecurrence] = useState(!!item.recurrence); + const [recurrenceFrequency, setRecurrenceFrequency] = useState( + item.recurrence?.frequency ?? "daily" + ); + const [isSaving, setIsSaving] = useState(false); + + // Close on escape + useEffect(() => { + const handleEscape = (e: KeyboardEvent) => { + if (e.key === "Escape") onClose(); + }; + window.addEventListener("keydown", handleEscape); + return () => window.removeEventListener("keydown", handleEscape); + }, [onClose]); + + const handleSave = async () => { + if (!canEdit) return; + + haptic("medium"); + setIsSaving(true); + + try { + await updateItem({ + itemId: item._id, + userDid, + legacyDid, + name: name !== item.name ? name : undefined, + description: description || undefined, + dueDate: dueDate ? new Date(dueDate).getTime() : undefined, + url: url || undefined, + recurrence: hasRecurrence + ? { frequency: recurrenceFrequency, interval: 1 } + : undefined, + clearDueDate: !dueDate && !!item.dueDate, + clearUrl: !url && !!item.url, + clearRecurrence: !hasRecurrence && !!item.recurrence, + }); + haptic("success"); + onClose(); + } catch (err) { + console.error("Failed to update item:", err); + haptic("error"); + } finally { + setIsSaving(false); + } + }; + + return ( +
+
e.stopPropagation()} + > + {/* Header */} +
+

+ {canEdit ? "Edit Item" : "Item Details"} +

+ +
+ + {/* Content */} +
+ {/* Name */} +
+ + setName(e.target.value)} + disabled={!canEdit} + className="w-full px-3 py-2 bg-gray-50 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded-lg text-sm text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-amber-500 disabled:opacity-50" + /> +
+ + {/* Description/Notes */} +
+ +