Add todolist workflow with freeze/archive and DB migrations#30
Add todolist workflow with freeze/archive and DB migrations#30macintoshwan merged 1 commit intomainfrom
Conversation
- Add todo workflow integrated with project tasks - Add project freeze and archive lifecycle controls - Unify frozen/archived badges and place at card front - Hide time display for frozen/archived projects - Add supabase migrations for todos/system/freeze/archive
Deploying artemis with
|
| Latest commit: |
882a124
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://ea68bb20.artemis-b7j.pages.dev |
| Branch Preview URL: | https://feature-todolist-merge.artemis-b7j.pages.dev |
There was a problem hiding this comment.
Pull request overview
Ports the todolist feature branch onto main by introducing DB migrations and wiring UI/state changes for a todo workflow plus project freeze/archive controls.
Changes:
- Added Supabase migrations for
todosplusprojectsflags (is_system,is_frozen,is_archived). - Extended frontend types/store/API to support todo data and system/temp project initialization.
- Updated UI to add todo section, project freeze/archive controls, state badges, and read-only behavior for frozen/archived projects.
Reviewed changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| supabase/migrations/001_create_todos_table.sql | Creates todos table with RLS policies and realtime publication. |
| supabase/migrations/002_add_system_project_flag.sql | Adds projects.is_system and unique constraint for per-user system project. |
| supabase/migrations/003_add_project_freeze_column.sql | Adds projects.is_frozen plus column comment. |
| supabase/migrations/004_add_project_archive_column.sql | Adds projects.is_archived plus an index. |
| src/types/index.ts | Extends Project and store types; introduces Todo/TodoItem types. |
| src/styles/base.css | Adds styles for project state tags, disabled status buttons, todo section, and section footer layout. |
| src/store/useProjectsStore.ts | Adds todo state/actions and selectors. |
| src/lib/api.ts | Adds ensureSystemTodoProject plus todo CRUD functions. |
| src/components/TodoListContainer.tsx | New container component for deriving todo items from tasks. |
| src/components/TodoList.tsx | New todo list UI (renders status controls and create action). |
| src/components/TaskList.tsx | Adds disabled/read-only behavior for tasks and updates status labels. |
| src/components/TaskEditModal.tsx | Adds support for default in-progress creation and deferred project resolution. |
| src/components/ProjectList.tsx | Adds freeze/archive controls, state badges, hides temp/system project, and suppresses remaining time for locked projects. |
| src/components/ProjectEditModal.tsx | Ensures new projects initialize new boolean flags. |
| src/components/ProjectDetail.tsx | Enforces read-only mode messaging and disables edits when frozen/archived. |
| src/components/App.tsx | Integrates todo section, temp project initialization, and checkin section footer actions. |
Comments suppressed due to low confidence (1)
src/components/ProjectList.tsx:50
planEndDate’suseMemologic depends onproject.is_frozen/project.is_archived, but those fields aren’t included in the dependency array. Toggling freeze/archive can leave a staleplanEndDateand keep showing the remaining-time badge. Includeproject?.is_frozenandproject?.is_archived(or just depend onproject) in the deps so the UI updates immediately.
const planEndDate = useMemo(() => {
if (project?.is_frozen || project?.is_archived) return null;
if (project?.plan_end_date) return project.plan_end_date;
if (!tasks || tasks.length === 0) return null;
// 所有任务已完成则不显示
const allDone = tasks.every((t) => t.completed);
if (allDone) return null;
let latest: Date | null = null;
for (const t of tasks) {
if (t.plan_end_date) {
const d = new Date(t.plan_end_date);
if (!latest || d > latest) latest = d;
}
}
return latest ? latest.toISOString() : null;
}, [project?.plan_end_date, tasks]);
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function isTempProject(project: Project | null | undefined): boolean { | ||
| if (!project) return false; | ||
| return project.is_system || project.category === 'system' || project.name === '临时'; | ||
| } |
There was a problem hiding this comment.
isTempProject helper is duplicated here and in ProjectList.tsx with slightly different typing. Consider moving it to a shared util (or types) to ensure consistent “temp/system project” detection logic across the app and avoid future drift.
| actual_start_date: string | null; | ||
| actual_end_date: string | null; | ||
| actual_duration: number | null; | ||
| actual_duration: number | null; |
There was a problem hiding this comment.
Line has trailing whitespace after the semicolon, which can cause noisy diffs and may violate formatting rules. Remove the extra space at the end of the line.
| actual_duration: number | null; | |
| actual_duration: number | null; |
| completed boolean DEFAULT false, | ||
| created_at timestamp with time zone DEFAULT now(), |
There was a problem hiding this comment.
completed and created_at are nullable in the table definition, but the TypeScript Todo type treats them as always present (boolean / string). Consider making them NOT NULL with defaults to keep DB constraints aligned with the app’s types and avoid unexpected null values in queries.
| completed boolean DEFAULT false, | |
| created_at timestamp with time zone DEFAULT now(), | |
| completed boolean NOT NULL DEFAULT false, | |
| created_at timestamp with time zone NOT NULL DEFAULT now(), |
| -- Enable Realtime | ||
| -- ============================================================ | ||
|
|
||
| ALTER PUBLICATION supabase_realtime ADD TABLE public.todos; |
There was a problem hiding this comment.
ALTER PUBLICATION supabase_realtime ADD TABLE public.todos; will fail if the table is already part of the publication (e.g., if migrations are re-applied or the publication was modified manually). If you want these migrations to be safely re-runnable like the IF NOT EXISTS column/index changes, wrap this in a conditional DO $$ ... $$ block that checks pg_publication_rel before adding the table.
| ALTER PUBLICATION supabase_realtime ADD TABLE public.todos; | |
| DO $$ | |
| BEGIN | |
| IF NOT EXISTS ( | |
| SELECT 1 | |
| FROM pg_publication_rel pr | |
| JOIN pg_publication p ON p.oid = pr.prpubid | |
| JOIN pg_class c ON c.oid = pr.prrelid | |
| JOIN pg_namespace n ON n.oid = c.relnamespace | |
| WHERE p.pubname = 'supabase_realtime' | |
| AND n.nspname = 'public' | |
| AND c.relname = 'todos' | |
| ) THEN | |
| ALTER PUBLICATION supabase_realtime ADD TABLE public.todos; | |
| END IF; | |
| END | |
| $$; |
| interface TaskItemProps { | ||
| taskId: number; | ||
| onEdit?: (taskId: number) => void; | ||
| isProjectFrozen?: boolean; | ||
| } | ||
|
|
||
| const TaskItem = memo(function TaskItem({ taskId, onEdit }: TaskItemProps) { | ||
| const TaskItem = memo(function TaskItem({ taskId, onEdit, isProjectFrozen = false }: TaskItemProps) { | ||
| const task = useProjectsStore(selectTask(taskId)); |
There was a problem hiding this comment.
isProjectFrozen is now being used to disable edits for both frozen and archived projects (it’s passed isProjectLocked from ProjectDetail). Consider renaming this prop to something like isProjectLocked/readOnly to reflect the actual semantics and avoid confusion as the feature set grows.
| // 添加独立 todo | ||
| for (const id of state.todoIds) { | ||
| const todo = state.todos[id]; | ||
| if (todo) { | ||
| items.push({ | ||
| id: todo.id, | ||
| name: todo.name, | ||
| type: 'standalone', | ||
| completed: todo.completed, | ||
| }); |
There was a problem hiding this comment.
This selector also pushes items with type: 'standalone', but TodoItem.type is currently defined as only 'project-task'. Either extend the TodoItem type to include a standalone variant (and add required fields like status), or keep this selector strictly to project-task items.
| import { useMemo } from 'react'; | ||
| import { useProjectsStore, selectProjectIds } from '../store/useProjectsStore'; | ||
| import { deriveTaskStatus } from '../types'; | ||
| import { TodoList } from './TodoList'; | ||
| import type { TodoItem } from '../types'; | ||
| import type { TaskStatus } from '../types'; | ||
|
|
||
| interface TodoListContainerProps { | ||
| onCreateTodoTask: () => void; | ||
| onChangeTodoStatus: (taskId: number, status: TaskStatus) => Promise<void>; |
There was a problem hiding this comment.
TodoListContainer is added but not referenced anywhere in the codebase (no imports/usages found). If it’s not part of the intended UI, consider removing it to avoid dead code; otherwise, wire it up (or replace the duplicated todo-item derivation currently in App.tsx).
|
|
||
| export const ProjectDetail = memo(function ProjectDetail({ projectId, onBack }: ProjectDetailProps) { | ||
| const project = useProjectsStore(selectProject(projectId)); | ||
| const isProjectLocked = Boolean(project?.is_frozen || project?.is_archived); |
There was a problem hiding this comment.
There’s an extra indentation level before const isProjectLocked = ... which makes this block misaligned with the surrounding code and can trip format/lint rules. Align it with the other const declarations in the component.
| const isProjectLocked = Boolean(project?.is_frozen || project?.is_archived); | |
| const isProjectLocked = Boolean(project?.is_frozen || project?.is_archived); |
| CREATE POLICY "Users can update their own todos" | ||
| ON public.todos | ||
| FOR UPDATE | ||
| USING (auth.uid() = user_id); |
There was a problem hiding this comment.
FOR UPDATE RLS policy only has a USING clause. Without a WITH CHECK (auth.uid() = user_id), a user who owns a row can update it and change user_id to another value, effectively bypassing ownership on the updated row. Add a matching WITH CHECK clause (or split into separate policies) so post-update rows are still constrained to the current user.
| USING (auth.uid() = user_id); | |
| USING (auth.uid() = user_id) | |
| WITH CHECK (auth.uid() = user_id); |
| useRealtimeSync(user?.id); | ||
|
|
||
| // 从 store 获取最基础的数据 | ||
| const { optimisticInsertProject } = useProjectsStore(); |
There was a problem hiding this comment.
useProjectsStore() is called without a selector to grab optimisticInsertProject, which subscribes this component to the entire store and can trigger re-renders on any store change. Prefer useProjectsStore((s) => s.optimisticInsertProject) (and similarly select only the needed actions) to keep renders scoped.
| const { optimisticInsertProject } = useProjectsStore(); | |
| const optimisticInsertProject = useProjectsStore((s) => s.optimisticInsertProject); |
Summary
Port the todolist feature set onto a branch based on
mainso it can be merged cleanly.Included changes
supabase/migrations/:001_create_todos_table.sql002_add_system_project_flag.sql003_add_project_freeze_column.sql004_add_project_archive_column.sqlValidation
npm run buildNotes
feature/todolistcould not open PR due unrelated history withmain.feature/todolist-merge) is created frommainto provide a clean merge path.