feat(app): tags, one per name, folded when devices made the same

A tag is its name: names are lowercase, a person has one tag per name,
and two of one name (made offline on two devices, or by renaming) fold
into the one with the smallest id, so every device ends up alike. The
colour lives in the payload. Deleting a card deletes its tags too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-01 16:12:17 +02:00
commit c2d4dfc8b6
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -16,7 +16,8 @@ import 'package:serverpod_database/serverpod_database.dart' as _isd;
/// A tag a person gives cards (ADR 0004): a name to study and filter by. Synced; its
/// space owns it. The name is content, so it lives in the payload, like a
/// studyset's title.
/// studyset's title. A tag is its name: a person has one tag per lowercase name, and
/// two with the same name, made on two devices offline, fold into one.
abstract class Tag
implements _isd.TableRow<_isc.UuidValue?>, _isc.ProtocolSerialization {
Tag._({
@ -64,7 +65,7 @@ abstract class Tag
/// The encryption key's id; always null (ADR 0002), as on studysets.
String? keyId;
/// The content as UTF-8 JSON: {"schemaVersion", "name"}.
/// The content as UTF-8 JSON: {"schemaVersion", "name", "color"}.
_idt.ByteData payload;
/// The encryption nonce; always null, like keyId.
@ -228,7 +229,7 @@ class TagTable extends _isd.Table<_isc.UuidValue?> {
/// The encryption key's id; always null (ADR 0002), as on studysets.
late final _isd.ColumnString keyId;
/// The content as UTF-8 JSON: {"schemaVersion", "name"}.
/// The content as UTF-8 JSON: {"schemaVersion", "name", "color"}.
late final _isd.ColumnByteData payload;
/// The encryption nonce; always null, like keyId.