Add PM extension (Projects, Work Logs, Comments) + Quill rich-text UI

Projects: Brick-backed model with list/detail screens and nav entry.
Work Logs: per-contributor time ledger, assignee running-clock +
helper logged minutes with Hours/Minutes input; pause-after-save and
helper-deduct prompts.
Comments: threaded comments with file attachments and Quill composer.
Detail screen: tabbed layout (Work Log / Comments / Activity),
editable title + description via edit dialog, Quill-rendered
descriptions with legacy plain-text fallback.
Create dialog: fixed-width (480px max), Quill description editor.
Shared QuillFieldEditor + QuillReadOnly widgets extracted for reuse.
RLS: hardened activity-log actor binding, immutable creator_id/
created_at triggers, SECURITY INVOKER visibility helper,
append-only work-log and scoped comment policies.
Brick migration for project_id FK on programmer_tasks.

Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
2026-09-27 16:06:26 +08:00
parent 627645056b
commit b307f66f2c
33 changed files with 2960 additions and 216 deletions
@@ -347,6 +347,7 @@ class ProgrammerTasksController {
required String category,
String? description,
String? assigneeId,
String? projectId,
int priority = 1,
}) async {
final userId = _client.auth.currentUser?.id;
@@ -363,6 +364,7 @@ class ProgrammerTasksController {
'priority': priority,
'assignee_id': assignee,
'creator_id': userId,
'project_id': ?projectId,
};
try {
@@ -384,6 +386,7 @@ class ProgrammerTasksController {
priority: priority,
assigneeId: assignee,
creatorId: userId,
projectId: projectId,
createdAt: now,
updatedAt: now,
);
@@ -481,6 +484,33 @@ class ProgrammerTasksController {
await _insertLog(taskId, 'updated', meta: {'fields': updates.keys.toList()});
}
/// Assigns the task to a project (pass null to clear it).
Future<void> setProject({
required String taskId,
required String? projectId,
}) async {
await _updateTaskRow(taskId, {'project_id': projectId});
await _insertLog(taskId, 'updated', meta: {
'fields': ['project_id'],
});
}
/// Records a time adjustment (negative [seconds]) against a task's worked
/// duration — e.g. when the current user helped on another task and chose to
/// deduct that time from their own running task.
Future<void> addAdjustment({
required String taskId,
required int seconds,
String? reason,
String? sourceTaskId,
}) async {
await _insertLog(taskId, 'adjustment', meta: {
'seconds': seconds,
'reason': ?reason,
'source_task_id': ?sourceTaskId,
});
}
/// Applies a field patch to a task row. When [onlyIfStartNull] is set,
/// `started_at` is dropped if the task already has one (preserves the first
/// execution start across pause/resume cycles).
@@ -578,6 +608,8 @@ ProgrammerTask _applyUpdateFields(ProgrammerTask t, Map<String, dynamic> f) {
assigneeId:
f.containsKey('assignee_id') ? f['assignee_id'] as String? : t.assigneeId,
creatorId: t.creatorId,
projectId:
f.containsKey('project_id') ? f['project_id'] as String? : t.projectId,
createdAt: t.createdAt,
startedAt: f.containsKey('started_at') ? parseOpt('started_at') : t.startedAt,
completedAt: