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
+6 -3
View File
@@ -29,14 +29,17 @@ DateTime? resolveExecutionStart(
}
/// Computes the effective worked duration, subtracting all paused intervals
/// between the resolved start and [endAt].
/// between the resolved start and [endAt], then subtracting [adjustment]
/// (e.g. time reassigned to another task when the user helped elsewhere).
///
/// [events] must be in DESCENDING order (newest first) — the same order
/// returned by activity-log queries with `ascending: false`.
/// returned by activity-log queries with `ascending: false`. The result never
/// goes below zero.
Duration computeEffectiveDuration({
required DateTime? fallbackStartedAt,
required DateTime endAt,
required List<DurationEvent> events,
Duration adjustment = Duration.zero,
}) {
final start = resolveExecutionStart(events, fallbackStartedAt);
if (start == null || !endAt.isAfter(start)) return Duration.zero;
@@ -67,7 +70,7 @@ Duration computeEffectiveDuration({
pausedTotal += endAt.difference(pausedSince);
}
final total = endAt.difference(start) - pausedTotal;
final total = endAt.difference(start) - pausedTotal - adjustment;
return total.isNegative ? Duration.zero : total;
}