mirror of
https://github.com/kuhyx/todo-app.git
synced 2026-11-13 11:00:48 +01:00
NoteRepository.bulkUpdate sets status/priority on many notes in one store write (mergeLogs + replaceAll; only changed fields plus updated_at get a clock). saveReplacing saves one note and tombstones others in one write. assignToRepo moves plain notes into the repo's open note as "## title" sections via addRepoTask, demoting body headings below "##" so a template note stays one section; notes already in a repo are refused. The seeded watch stream moved to log_watch.dart for the 250-line cap. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWXdsB7CQPNXVN829G1
60 lines
2.2 KiB
Dart
60 lines
2.2 KiB
Dart
/// Partial logs for the multi-note writes: merged in one store write.
|
|
///
|
|
/// Each write re-serialises the whole log (over a megabyte on a real
|
|
/// device), so a bulk action must be one partial log merged once, never one
|
|
/// upsert per note. Merging is per field, so a field left out keeps its value
|
|
/// and its clock (`created_by` included).
|
|
library;
|
|
|
|
import 'package:crdt_sync/crdt_sync.dart';
|
|
import 'package:todo/data/note.dart';
|
|
import 'package:todo/data/note_record.dart';
|
|
|
|
/// Partial records setting [priority] and/or [status] on the live notes of
|
|
/// [log] among [ids], all at [hlc].
|
|
///
|
|
/// Only fields whose value changes are written, plus `updated_at` = [now]
|
|
/// (so "last updated" sorting and newer-wins import see the edit). A note
|
|
/// with nothing to change, an unknown id and a deleted note are left out.
|
|
Log planBulkUpdate(
|
|
Log log,
|
|
Set<String> ids, {
|
|
required Hlc hlc,
|
|
required DateTime now,
|
|
Priority? priority,
|
|
Status? status,
|
|
}) {
|
|
final plan = <String, Record>{};
|
|
for (final id in ids) {
|
|
final current = log[id];
|
|
if (current == null || current.deleted) continue;
|
|
final note = noteFromRecord(current);
|
|
final next = note.copyWith(priority: priority, status: status);
|
|
if (next.priority == note.priority && next.status == note.status) continue;
|
|
plan[id] = Record(
|
|
id: id,
|
|
fields: {
|
|
for (final MapEntry(:key, :value) in noteFields(
|
|
next.copyWith(updatedAt: now),
|
|
hlc,
|
|
).entries)
|
|
if (current.fields[key]?.$1 != value.$1) key: value,
|
|
},
|
|
);
|
|
}
|
|
return plan;
|
|
}
|
|
|
|
/// [note] at [hlc] plus a tombstone for each live note of [log] in
|
|
/// [removed] (other than [note] itself).
|
|
///
|
|
/// A merge, not a replace: a field [note] leaves unset keeps its stored
|
|
/// value, so this suits notes that only gain content (the repo note an
|
|
/// assign appends to), not edits that clear a field.
|
|
Log planReplace(Log log, Note note, Iterable<String> removed, Hlc hlc) => {
|
|
for (final id in removed)
|
|
if (log[id] case final r? when !r.deleted && id != note.id)
|
|
id: Record(id: id, fields: const {}, deleted: true, deletedHlc: hlc),
|
|
note.id: Record(id: note.id, fields: noteFields(note, hlc)),
|
|
};
|