aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-02-25 23:02:35 +0900
committernsfisis <nsfisis@gmail.com>2026-02-25 23:02:35 +0900
commit38b8fc0e9927c4146b4c8b309b2bcc644abd63d0 (patch)
treef76ba23251645e552fccd201362064b06de50bdd
parent7a77e72bb49ed3990a0c4581292a37a8a4f35231 (diff)
downloadkioku-main.tar.gz
kioku-main.tar.zst
kioku-main.zip
feat(decks): add default note type setting per deckHEADmain
Allow each deck to specify a default note type that is auto-selected when creating new notes. Includes DB schema migration, server API updates, sync layer support, and UI for editing the default in the deck settings modal. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
-rw-r--r--drizzle/0005_certain_stick.sql3
-rw-r--r--drizzle/meta/0005_snapshot.json992
-rw-r--r--drizzle/meta/_journal.json7
-rw-r--r--src/client/atoms/decks.ts1
-rw-r--r--src/client/components/CreateNoteModal.tsx15
-rw-r--r--src/client/components/EditDeckModal.test.tsx58
-rw-r--r--src/client/components/EditDeckModal.tsx60
-rw-r--r--src/client/db/index.test.ts1
-rw-r--r--src/client/db/index.ts12
-rw-r--r--src/client/db/repositories.test.ts16
-rw-r--r--src/client/pages/DeckCardsPage.test.tsx1
-rw-r--r--src/client/pages/DeckCardsPage.tsx10
-rw-r--r--src/client/pages/DeckDetailPage.test.tsx1
-rw-r--r--src/client/pages/DeckDetailPage.tsx5
-rw-r--r--src/client/pages/HomePage.test.tsx4
-rw-r--r--src/client/sync/conflict.test.ts20
-rw-r--r--src/client/sync/conflict.ts1
-rw-r--r--src/client/sync/crdt/concurrent-edits.test.ts1
-rw-r--r--src/client/sync/crdt/document-manager.test.ts9
-rw-r--r--src/client/sync/crdt/document-manager.ts3
-rw-r--r--src/client/sync/crdt/migration.test.ts1
-rw-r--r--src/client/sync/crdt/repositories.test.ts6
-rw-r--r--src/client/sync/crdt/types.test.ts2
-rw-r--r--src/client/sync/crdt/types.ts1
-rw-r--r--src/client/sync/manager.test.ts2
-rw-r--r--src/client/sync/pull.test.ts21
-rw-r--r--src/client/sync/pull.ts2
-rw-r--r--src/client/sync/push.test.ts17
-rw-r--r--src/client/sync/push.ts2
-rw-r--r--src/client/sync/queue.test.ts12
-rw-r--r--src/server/db/schema.ts3
-rw-r--r--src/server/repositories/deck.test.ts1
-rw-r--r--src/server/repositories/deck.ts3
-rw-r--r--src/server/repositories/sync.test.ts1
-rw-r--r--src/server/repositories/sync.ts3
-rw-r--r--src/server/repositories/types.ts3
-rw-r--r--src/server/routes/cards.test.ts1
-rw-r--r--src/server/routes/decks.test.ts1
-rw-r--r--src/server/routes/decks.ts1
-rw-r--r--src/server/routes/notes.test.ts1
-rw-r--r--src/server/routes/study.test.ts1
-rw-r--r--src/server/routes/sync.test.ts8
-rw-r--r--src/server/routes/sync.ts1
-rw-r--r--src/server/schemas/index.ts3
44 files changed, 1296 insertions, 21 deletions
diff --git a/drizzle/0005_certain_stick.sql b/drizzle/0005_certain_stick.sql
new file mode 100644
index 0000000..379ee7c
--- /dev/null
+++ b/drizzle/0005_certain_stick.sql
@@ -0,0 +1,3 @@
+ALTER TABLE "decks" ADD COLUMN "default_note_type_id" uuid;--> statement-breakpoint
+ALTER TABLE "decks" ADD CONSTRAINT "decks_default_note_type_id_note_types_id_fk" FOREIGN KEY ("default_note_type_id") REFERENCES "public"."note_types"("id") ON DELETE no action ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "decks" DROP COLUMN "new_cards_per_day"; \ No newline at end of file
diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json
new file mode 100644
index 0000000..489a3c3
--- /dev/null
+++ b/drizzle/meta/0005_snapshot.json
@@ -0,0 +1,992 @@
+{
+ "id": "dd73ed21-7aea-4a0a-a0bf-04bb8076e635",
+ "prevId": "4978370b-924e-40b1-9fd4-770994e1a90a",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.cards": {
+ "name": "cards",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deck_id": {
+ "name": "deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note_id": {
+ "name": "note_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_reversed": {
+ "name": "is_reversed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "front": {
+ "name": "front",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "back": {
+ "name": "back",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "due": {
+ "name": "due",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "stability": {
+ "name": "stability",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "difficulty": {
+ "name": "difficulty",
+ "type": "real",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "elapsed_days": {
+ "name": "elapsed_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "scheduled_days": {
+ "name": "scheduled_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "reps": {
+ "name": "reps",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "lapses": {
+ "name": "lapses",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "last_review": {
+ "name": "last_review",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_version": {
+ "name": "sync_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "cards_deck_id_decks_id_fk": {
+ "name": "cards_deck_id_decks_id_fk",
+ "tableFrom": "cards",
+ "tableTo": "decks",
+ "columnsFrom": [
+ "deck_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "cards_note_id_notes_id_fk": {
+ "name": "cards_note_id_notes_id_fk",
+ "tableFrom": "cards",
+ "tableTo": "notes",
+ "columnsFrom": [
+ "note_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.decks": {
+ "name": "decks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "default_note_type_id": {
+ "name": "default_note_type_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_version": {
+ "name": "sync_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "decks_user_id_users_id_fk": {
+ "name": "decks_user_id_users_id_fk",
+ "tableFrom": "decks",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "decks_default_note_type_id_note_types_id_fk": {
+ "name": "decks_default_note_type_id_note_types_id_fk",
+ "tableFrom": "decks",
+ "tableTo": "note_types",
+ "columnsFrom": [
+ "default_note_type_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.note_field_types": {
+ "name": "note_field_types",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "note_type_id": {
+ "name": "note_type_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "order": {
+ "name": "order",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "field_type": {
+ "name": "field_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'text'"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_version": {
+ "name": "sync_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "note_field_types_note_type_id_note_types_id_fk": {
+ "name": "note_field_types_note_type_id_note_types_id_fk",
+ "tableFrom": "note_field_types",
+ "tableTo": "note_types",
+ "columnsFrom": [
+ "note_type_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.note_field_values": {
+ "name": "note_field_values",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "note_id": {
+ "name": "note_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note_field_type_id": {
+ "name": "note_field_type_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "sync_version": {
+ "name": "sync_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "note_field_values_note_id_notes_id_fk": {
+ "name": "note_field_values_note_id_notes_id_fk",
+ "tableFrom": "note_field_values",
+ "tableTo": "notes",
+ "columnsFrom": [
+ "note_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "note_field_values_note_field_type_id_note_field_types_id_fk": {
+ "name": "note_field_values_note_field_type_id_note_field_types_id_fk",
+ "tableFrom": "note_field_values",
+ "tableTo": "note_field_types",
+ "columnsFrom": [
+ "note_field_type_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.note_types": {
+ "name": "note_types",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "front_template": {
+ "name": "front_template",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "back_template": {
+ "name": "back_template",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "is_reversible": {
+ "name": "is_reversible",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_version": {
+ "name": "sync_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "note_types_user_id_users_id_fk": {
+ "name": "note_types_user_id_users_id_fk",
+ "tableFrom": "note_types",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.notes": {
+ "name": "notes",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "deck_id": {
+ "name": "deck_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note_type_id": {
+ "name": "note_type_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "deleted_at": {
+ "name": "deleted_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_version": {
+ "name": "sync_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "notes_deck_id_decks_id_fk": {
+ "name": "notes_deck_id_decks_id_fk",
+ "tableFrom": "notes",
+ "tableTo": "decks",
+ "columnsFrom": [
+ "deck_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "notes_note_type_id_note_types_id_fk": {
+ "name": "notes_note_type_id_note_types_id_fk",
+ "tableFrom": "notes",
+ "tableTo": "note_types",
+ "columnsFrom": [
+ "note_type_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.refresh_tokens": {
+ "name": "refresh_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_hash": {
+ "name": "token_hash",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "refresh_tokens_user_id_users_id_fk": {
+ "name": "refresh_tokens_user_id_users_id_fk",
+ "tableFrom": "refresh_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.review_logs": {
+ "name": "review_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "card_id": {
+ "name": "card_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "rating": {
+ "name": "rating",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "state": {
+ "name": "state",
+ "type": "smallint",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scheduled_days": {
+ "name": "scheduled_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "elapsed_days": {
+ "name": "elapsed_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reviewed_at": {
+ "name": "reviewed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "duration_ms": {
+ "name": "duration_ms",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sync_version": {
+ "name": "sync_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "review_logs_card_id_cards_id_fk": {
+ "name": "review_logs_card_id_cards_id_fk",
+ "tableFrom": "review_logs",
+ "tableTo": "cards",
+ "columnsFrom": [
+ "card_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ },
+ "review_logs_user_id_users_id_fk": {
+ "name": "review_logs_user_id_users_id_fk",
+ "tableFrom": "review_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "username": {
+ "name": "username",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "varchar(255)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_username_unique": {
+ "name": "users_username_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "username"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.crdt_documents": {
+ "name": "crdt_documents",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "varchar(50)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "binary": {
+ "name": "binary",
+ "type": "varchar(1048576)",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sync_version": {
+ "name": "sync_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "crdt_documents_user_entity_idx": {
+ "name": "crdt_documents_user_entity_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "crdt_documents_entity_type_idx": {
+ "name": "crdt_documents_entity_type_idx",
+ "columns": [
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "crdt_documents_sync_version_idx": {
+ "name": "crdt_documents_sync_version_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "sync_version",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "crdt_documents_user_id_users_id_fk": {
+ "name": "crdt_documents_user_id_users_id_fk",
+ "tableFrom": "crdt_documents",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+} \ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index fe8279e..9d2626d 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -36,6 +36,13 @@
"when": 1767164715079,
"tag": "0004_clean_leopardon",
"breakpoints": true
+ },
+ {
+ "idx": 5,
+ "version": "7",
+ "when": 1772025984247,
+ "tag": "0005_certain_stick",
+ "breakpoints": true
}
]
} \ No newline at end of file
diff --git a/src/client/atoms/decks.ts b/src/client/atoms/decks.ts
index 8c8397f..0d20586 100644
--- a/src/client/atoms/decks.ts
+++ b/src/client/atoms/decks.ts
@@ -6,6 +6,7 @@ export interface Deck {
id: string;
name: string;
description: string | null;
+ defaultNoteTypeId: string | null;
dueCardCount: number;
newCardCount: number;
totalCardCount: number;
diff --git a/src/client/components/CreateNoteModal.tsx b/src/client/components/CreateNoteModal.tsx
index 912aea8..cc39bf6 100644
--- a/src/client/components/CreateNoteModal.tsx
+++ b/src/client/components/CreateNoteModal.tsx
@@ -27,6 +27,7 @@ interface NoteTypeSummary {
interface CreateNoteModalProps {
isOpen: boolean;
deckId: string;
+ defaultNoteTypeId?: string | null;
onClose: () => void;
onNoteCreated: () => void;
}
@@ -34,6 +35,7 @@ interface CreateNoteModalProps {
export function CreateNoteModal({
isOpen,
deckId,
+ defaultNoteTypeId,
onClose,
onNoteCreated,
}: CreateNoteModalProps) {
@@ -88,10 +90,13 @@ export function CreateNoteModal({
setNoteTypes(data.noteTypes);
setHasLoadedNoteTypes(true);
- // Auto-select first note type if available
- const firstNoteType = data.noteTypes[0];
- if (firstNoteType) {
- await fetchNoteTypeDetails(firstNoteType.id);
+ // Auto-select default note type if specified, otherwise first
+ const targetNoteType =
+ (defaultNoteTypeId &&
+ data.noteTypes.find((nt) => nt.id === defaultNoteTypeId)) ||
+ data.noteTypes[0];
+ if (targetNoteType) {
+ await fetchNoteTypeDetails(targetNoteType.id);
}
} catch (err) {
if (err instanceof ApiClientError) {
@@ -102,7 +107,7 @@ export function CreateNoteModal({
} finally {
setIsLoadingNoteTypes(false);
}
- }, [fetchNoteTypeDetails]);
+ }, [fetchNoteTypeDetails, defaultNoteTypeId]);
useEffect(() => {
if (isOpen && !hasLoadedNoteTypes) {
diff --git a/src/client/components/EditDeckModal.test.tsx b/src/client/components/EditDeckModal.test.tsx
index b22cb1d..248c74f 100644
--- a/src/client/components/EditDeckModal.test.tsx
+++ b/src/client/components/EditDeckModal.test.tsx
@@ -8,6 +8,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
const mockPut = vi.fn();
const mockHandleResponse = vi.fn();
+const mockGetNoteTypes = vi.fn();
+
vi.mock("../api/client", () => ({
apiClient: {
rpc: {
@@ -17,6 +19,9 @@ vi.mock("../api/client", () => ({
$put: (args: unknown) => mockPut(args),
},
},
+ "note-types": {
+ $get: () => mockGetNoteTypes(),
+ },
},
},
handleResponse: (res: unknown) => mockHandleResponse(res),
@@ -42,6 +47,7 @@ describe("EditDeckModal", () => {
id: "deck-123",
name: "Test Deck",
description: "Test description",
+ defaultNoteTypeId: null as string | null,
};
const defaultProps = {
@@ -51,15 +57,25 @@ describe("EditDeckModal", () => {
onDeckUpdated: vi.fn(),
};
+ const noteTypesResponse = { ok: true, _type: "noteTypes" };
+ const putResponse = { ok: true, _type: "put" };
+
beforeEach(() => {
vi.clearAllMocks();
- mockPut.mockResolvedValue({ ok: true });
- mockHandleResponse.mockResolvedValue({
- deck: {
- id: "deck-123",
- name: "Test Deck",
- description: "Test description",
- },
+ mockPut.mockResolvedValue(putResponse);
+ mockGetNoteTypes.mockResolvedValue(noteTypesResponse);
+ mockHandleResponse.mockImplementation((res: unknown) => {
+ if (res === noteTypesResponse) {
+ return Promise.resolve({ noteTypes: [] });
+ }
+ return Promise.resolve({
+ deck: {
+ id: "deck-123",
+ name: "Test Deck",
+ description: "Test description",
+ defaultNoteTypeId: null,
+ },
+ });
});
});
@@ -187,6 +203,7 @@ describe("EditDeckModal", () => {
json: {
name: "Updated Deck",
description: "Test description",
+ defaultNoteTypeId: null,
},
});
});
@@ -220,6 +237,7 @@ describe("EditDeckModal", () => {
json: {
name: "Test Deck",
description: "New description",
+ defaultNoteTypeId: null,
},
});
});
@@ -243,6 +261,7 @@ describe("EditDeckModal", () => {
json: {
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
},
});
});
@@ -266,6 +285,7 @@ describe("EditDeckModal", () => {
json: {
name: "Deck",
description: "Description",
+ defaultNoteTypeId: null,
},
});
});
@@ -299,12 +319,16 @@ describe("EditDeckModal", () => {
it("displays API error message", async () => {
const user = userEvent.setup();
+ render(<EditDeckModal {...defaultProps} />);
+
+ // Wait for note types to load, then override handleResponse for the PUT
+ await waitFor(() => {
+ expect(mockGetNoteTypes).toHaveBeenCalled();
+ });
mockHandleResponse.mockRejectedValue(
new ApiClientError("Deck name already exists", 400),
);
- render(<EditDeckModal {...defaultProps} />);
-
await user.click(screen.getByRole("button", { name: "Save Changes" }));
await waitFor(() => {
@@ -333,12 +357,16 @@ describe("EditDeckModal", () => {
it("displays error when handleResponse throws", async () => {
const user = userEvent.setup();
+ render(<EditDeckModal {...defaultProps} />);
+
+ // Wait for note types to load, then override handleResponse for the PUT
+ await waitFor(() => {
+ expect(mockGetNoteTypes).toHaveBeenCalled();
+ });
mockHandleResponse.mockRejectedValue(
new ApiClientError("Not authenticated", 401),
);
- render(<EditDeckModal {...defaultProps} />);
-
await user.click(screen.getByRole("button", { name: "Save Changes" }));
await waitFor(() => {
@@ -374,12 +402,16 @@ describe("EditDeckModal", () => {
const user = userEvent.setup();
const onClose = vi.fn();
- mockHandleResponse.mockRejectedValue(new ApiClientError("Some error", 400));
-
const { rerender } = render(
<EditDeckModal {...defaultProps} onClose={onClose} />,
);
+ // Wait for note types to load, then override handleResponse for the PUT
+ await waitFor(() => {
+ expect(mockGetNoteTypes).toHaveBeenCalled();
+ });
+ mockHandleResponse.mockRejectedValue(new ApiClientError("Some error", 400));
+
// Trigger error
await user.click(screen.getByRole("button", { name: "Save Changes" }));
await waitFor(() => {
diff --git a/src/client/components/EditDeckModal.tsx b/src/client/components/EditDeckModal.tsx
index 8e95295..9a79de8 100644
--- a/src/client/components/EditDeckModal.tsx
+++ b/src/client/components/EditDeckModal.tsx
@@ -1,10 +1,16 @@
-import { type FormEvent, useEffect, useState } from "react";
+import { type FormEvent, useCallback, useEffect, useState } from "react";
import { ApiClientError, apiClient } from "../api";
interface Deck {
id: string;
name: string;
description: string | null;
+ defaultNoteTypeId: string | null;
+}
+
+interface NoteTypeSummary {
+ id: string;
+ name: string;
}
interface EditDeckModalProps {
@@ -22,18 +28,45 @@ export function EditDeckModal({
}: EditDeckModalProps) {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
+ const [defaultNoteTypeId, setDefaultNoteTypeId] = useState<string | null>(
+ null,
+ );
+ const [noteTypes, setNoteTypes] = useState<NoteTypeSummary[]>([]);
+ const [isLoadingNoteTypes, setIsLoadingNoteTypes] = useState(false);
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
+ const fetchNoteTypes = useCallback(async () => {
+ setIsLoadingNoteTypes(true);
+ try {
+ const res = await apiClient.rpc.api["note-types"].$get();
+ const data = await apiClient.handleResponse<{
+ noteTypes: NoteTypeSummary[];
+ }>(res);
+ setNoteTypes(data.noteTypes);
+ } catch {
+ // Non-critical: note type list is optional
+ } finally {
+ setIsLoadingNoteTypes(false);
+ }
+ }, []);
+
// Sync form state when deck changes
useEffect(() => {
if (deck) {
setName(deck.name);
setDescription(deck.description ?? "");
+ setDefaultNoteTypeId(deck.defaultNoteTypeId);
setError(null);
}
}, [deck]);
+ useEffect(() => {
+ if (isOpen) {
+ fetchNoteTypes();
+ }
+ }, [isOpen, fetchNoteTypes]);
+
const handleClose = () => {
setError(null);
onClose();
@@ -52,6 +85,7 @@ export function EditDeckModal({
json: {
name: name.trim(),
description: description.trim() || null,
+ defaultNoteTypeId: defaultNoteTypeId || null,
},
});
await apiClient.handleResponse(res);
@@ -147,6 +181,30 @@ export function EditDeckModal({
/>
</div>
+ <div>
+ <label
+ htmlFor="edit-deck-default-note-type"
+ className="block text-sm font-medium text-slate mb-1.5"
+ >
+ Default Note Type{" "}
+ <span className="text-muted font-normal">(optional)</span>
+ </label>
+ <select
+ id="edit-deck-default-note-type"
+ value={defaultNoteTypeId ?? ""}
+ onChange={(e) => setDefaultNoteTypeId(e.target.value || null)}
+ disabled={isSubmitting || isLoadingNoteTypes}
+ className="w-full px-4 py-2.5 bg-ivory border border-border rounded-lg text-slate transition-all duration-200 hover:border-muted focus:border-primary focus:ring-2 focus:ring-primary/10 disabled:opacity-50 disabled:cursor-not-allowed"
+ >
+ <option value="">None</option>
+ {noteTypes.map((nt) => (
+ <option key={nt.id} value={nt.id}>
+ {nt.name}
+ </option>
+ ))}
+ </select>
+ </div>
+
<div className="flex gap-3 justify-end pt-2">
<button
type="button"
diff --git a/src/client/db/index.test.ts b/src/client/db/index.test.ts
index 0dd3758..32011b5 100644
--- a/src/client/db/index.test.ts
+++ b/src/client/db/index.test.ts
@@ -64,6 +64,7 @@ describe("KiokuDatabase", () => {
userId: "user-1",
name: "Test Deck",
description: "A test deck",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
diff --git a/src/client/db/index.ts b/src/client/db/index.ts
index 50d8bbd..a3fbd49 100644
--- a/src/client/db/index.ts
+++ b/src/client/db/index.ts
@@ -77,6 +77,7 @@ export interface LocalDeck {
userId: string;
name: string;
description: string | null;
+ defaultNoteTypeId: string | null;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
@@ -251,6 +252,17 @@ export class KiokuDatabase extends Dexie {
notes: "id, deckId, noteTypeId, updatedAt",
noteFieldValues: "id, noteId, noteFieldTypeId, updatedAt",
});
+
+ // Version 4: Add defaultNoteTypeId to decks (no index change needed)
+ this.version(4).stores({
+ decks: "id, userId, updatedAt",
+ cards: "id, deckId, noteId, updatedAt, due, state",
+ reviewLogs: "id, cardId, userId, reviewedAt",
+ noteTypes: "id, userId, updatedAt",
+ noteFieldTypes: "id, noteTypeId, updatedAt",
+ notes: "id, deckId, noteTypeId, updatedAt",
+ noteFieldValues: "id, noteId, noteFieldTypeId, updatedAt",
+ });
}
}
diff --git a/src/client/db/repositories.test.ts b/src/client/db/repositories.test.ts
index b461990..6a77760 100644
--- a/src/client/db/repositories.test.ts
+++ b/src/client/db/repositories.test.ts
@@ -33,6 +33,7 @@ describe("localDeckRepository", () => {
userId: "user-1",
name: "Test Deck",
description: "A test deck",
+ defaultNoteTypeId: null,
});
expect(deck.id).toBeDefined();
@@ -51,6 +52,7 @@ describe("localDeckRepository", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const found = await db.decks.get(created.id);
@@ -64,6 +66,7 @@ describe("localDeckRepository", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const found = await localDeckRepository.findById(created.id);
@@ -82,16 +85,19 @@ describe("localDeckRepository", () => {
userId: "user-1",
name: "Deck 1",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.create({
userId: "user-1",
name: "Deck 2",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.create({
userId: "user-2",
name: "Other User Deck",
description: null,
+ defaultNoteTypeId: null,
});
const decks = await localDeckRepository.findByUserId("user-1");
@@ -104,6 +110,7 @@ describe("localDeckRepository", () => {
userId: "user-1",
name: "Deleted Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.delete(deck.id);
@@ -118,6 +125,7 @@ describe("localDeckRepository", () => {
userId: "user-1",
name: "Original Name",
description: null,
+ defaultNoteTypeId: null,
});
const updated = await localDeckRepository.update(deck.id, {
@@ -147,6 +155,7 @@ describe("localDeckRepository", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const result = await localDeckRepository.delete(deck.id);
@@ -169,11 +178,13 @@ describe("localDeckRepository", () => {
userId: "user-1",
name: "Unsynced",
description: null,
+ defaultNoteTypeId: null,
});
const deck2 = await localDeckRepository.create({
userId: "user-1",
name: "Synced",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck2.id, 1);
@@ -189,6 +200,7 @@ describe("localDeckRepository", () => {
userId: "user-1",
name: "Test",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck.id, 5);
@@ -212,6 +224,7 @@ describe("localCardRepository", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
deckId = deck.id;
});
@@ -411,6 +424,7 @@ describe("localReviewLogRepository", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
deckId = deck.id;
@@ -935,6 +949,7 @@ describe("localNoteRepository", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
deckId = deck.id;
@@ -1094,6 +1109,7 @@ describe("localNoteFieldValueRepository", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const noteType = await localNoteTypeRepository.create({
diff --git a/src/client/pages/DeckCardsPage.test.tsx b/src/client/pages/DeckCardsPage.test.tsx
index 58107d4..6e8f444 100644
--- a/src/client/pages/DeckCardsPage.test.tsx
+++ b/src/client/pages/DeckCardsPage.test.tsx
@@ -73,6 +73,7 @@ const mockDeck = {
id: "deck-1",
name: "Japanese Vocabulary",
description: "Common Japanese words",
+ defaultNoteTypeId: null,
dueCardCount: 0,
newCardCount: 0,
totalCardCount: 0,
diff --git a/src/client/pages/DeckCardsPage.tsx b/src/client/pages/DeckCardsPage.tsx
index 8c839da..b791eac 100644
--- a/src/client/pages/DeckCardsPage.tsx
+++ b/src/client/pages/DeckCardsPage.tsx
@@ -22,7 +22,12 @@ import {
} from "react";
import { useDebouncedCallback } from "use-debounce";
import { Link, useParams } from "wouter";
-import { type Card, cardsByDeckAtomFamily, deckByIdAtomFamily } from "../atoms";
+import {
+ type Card,
+ cardsByDeckAtomFamily,
+ type Deck,
+ deckByIdAtomFamily,
+} from "../atoms";
import { CreateNoteModal } from "../components/CreateNoteModal";
import { DeleteCardModal } from "../components/DeleteCardModal";
import { DeleteNoteModal } from "../components/DeleteNoteModal";
@@ -638,6 +643,9 @@ export function DeckCardsPage() {
<CreateNoteModal
isOpen={isCreateModalOpen}
deckId={deckId}
+ defaultNoteTypeId={
+ queryClient.getQueryData<Deck>(["decks", deckId])?.defaultNoteTypeId
+ }
onClose={() => setIsCreateModalOpen(false)}
onNoteCreated={handleCardMutation}
/>
diff --git a/src/client/pages/DeckDetailPage.test.tsx b/src/client/pages/DeckDetailPage.test.tsx
index bb9d80c..c473275 100644
--- a/src/client/pages/DeckDetailPage.test.tsx
+++ b/src/client/pages/DeckDetailPage.test.tsx
@@ -60,6 +60,7 @@ const mockDeck = {
id: "deck-1",
name: "Japanese Vocabulary",
description: "Common Japanese words",
+ defaultNoteTypeId: null,
dueCardCount: 0,
newCardCount: 0,
totalCardCount: 0,
diff --git a/src/client/pages/DeckDetailPage.tsx b/src/client/pages/DeckDetailPage.tsx
index 0a02051..99e919b 100644
--- a/src/client/pages/DeckDetailPage.tsx
+++ b/src/client/pages/DeckDetailPage.tsx
@@ -8,7 +8,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useAtomValue } from "jotai";
import { Suspense, useState } from "react";
import { Link, useParams } from "wouter";
-import { cardsByDeckAtomFamily, deckByIdAtomFamily } from "../atoms";
+import { cardsByDeckAtomFamily, type Deck, deckByIdAtomFamily } from "../atoms";
import { CreateNoteModal } from "../components/CreateNoteModal";
import { ErrorBoundary } from "../components/ErrorBoundary";
import { LoadingSpinner } from "../components/LoadingSpinner";
@@ -204,6 +204,9 @@ export function DeckDetailPage() {
<CreateNoteModal
isOpen={isCreateModalOpen}
deckId={deckId}
+ defaultNoteTypeId={
+ queryClient.getQueryData<Deck>(["decks", deckId])?.defaultNoteTypeId
+ }
onClose={() => setIsCreateModalOpen(false)}
onNoteCreated={() => {
queryClient.invalidateQueries({
diff --git a/src/client/pages/HomePage.test.tsx b/src/client/pages/HomePage.test.tsx
index de7a768..adcc651 100644
--- a/src/client/pages/HomePage.test.tsx
+++ b/src/client/pages/HomePage.test.tsx
@@ -92,6 +92,7 @@ const mockDecks = [
id: "deck-1",
name: "Japanese Vocabulary",
description: "Common Japanese words",
+ defaultNoteTypeId: null,
dueCardCount: 5,
newCardCount: 0,
totalCardCount: 100,
@@ -103,6 +104,7 @@ const mockDecks = [
id: "deck-2",
name: "Spanish Verbs",
description: null,
+ defaultNoteTypeId: null,
dueCardCount: 0,
newCardCount: 0,
totalCardCount: 0,
@@ -258,6 +260,7 @@ describe("HomePage", () => {
id: "deck-1",
name: "No Description Deck",
description: null,
+ defaultNoteTypeId: null,
dueCardCount: 0,
newCardCount: 0,
totalCardCount: 0,
@@ -340,6 +343,7 @@ describe("HomePage", () => {
id: "deck-new",
name: "New Deck",
description: "A new deck",
+ defaultNoteTypeId: null,
dueCardCount: 0,
newCardCount: 0,
totalCardCount: 0,
diff --git a/src/client/sync/conflict.test.ts b/src/client/sync/conflict.test.ts
index d11e150..ba6586f 100644
--- a/src/client/sync/conflict.test.ts
+++ b/src/client/sync/conflict.test.ts
@@ -155,6 +155,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Local Name",
description: "Local description",
+ defaultNoteTypeId: null,
});
const serverDeck = {
@@ -165,6 +166,7 @@ describe("ConflictResolver", () => {
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-03"),
deletedAt: null,
+ defaultNoteTypeId: null,
syncVersion: 5,
};
@@ -186,6 +188,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const localCard = await localCardRepository.create({
@@ -236,11 +239,13 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Local Deck 1",
description: null,
+ defaultNoteTypeId: null,
});
const deck2 = await localDeckRepository.create({
userId: "user-1",
name: "Local Deck 2",
description: null,
+ defaultNoteTypeId: null,
});
const pushResult: SyncPushResult = {
@@ -267,6 +272,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Server Deck 1",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
@@ -277,6 +283,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Server Deck 2",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
@@ -306,6 +313,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const card = await localCardRepository.create({
@@ -388,6 +396,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Server Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
@@ -415,6 +424,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Local Only Deck",
description: null,
+ defaultNoteTypeId: null,
});
const pushResult: SyncPushResult = {
@@ -451,6 +461,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Local Name",
description: null,
+ defaultNoteTypeId: null,
});
const pushResult: SyncPushResult = {
@@ -471,6 +482,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Server Name",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
@@ -501,6 +513,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Local Deck Name",
description: "Local description",
+ defaultNoteTypeId: null,
});
// Store local CRDT document
@@ -518,6 +531,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Server Deck Name",
description: "Server description",
+ defaultNoteTypeId: null,
createdAt: localDeck.createdAt,
updatedAt: new Date(Date.now() + 1000),
deletedAt: null,
@@ -575,6 +589,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Local Name",
description: null,
+ defaultNoteTypeId: null,
});
const serverDeck = {
@@ -582,6 +597,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Server Name",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
@@ -632,6 +648,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Local Name",
description: null,
+ defaultNoteTypeId: null,
});
const serverDeck = {
@@ -639,6 +656,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Server Name",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
@@ -680,6 +698,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Local Name",
description: null,
+ defaultNoteTypeId: null,
});
const serverDeck = {
@@ -687,6 +706,7 @@ describe("ConflictResolver", () => {
userId: "user-1",
name: "Server Name",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
diff --git a/src/client/sync/conflict.ts b/src/client/sync/conflict.ts
index 64287a1..fcb5bf4 100644
--- a/src/client/sync/conflict.ts
+++ b/src/client/sync/conflict.ts
@@ -67,6 +67,7 @@ function serverDeckToLocal(deck: ServerDeck): LocalDeck {
userId: deck.userId,
name: deck.name,
description: deck.description,
+ defaultNoteTypeId: deck.defaultNoteTypeId,
createdAt: new Date(deck.createdAt),
updatedAt: new Date(deck.updatedAt),
deletedAt: deck.deletedAt ? new Date(deck.deletedAt) : null,
diff --git a/src/client/sync/crdt/concurrent-edits.test.ts b/src/client/sync/crdt/concurrent-edits.test.ts
index d55b233..e9d7b58 100644
--- a/src/client/sync/crdt/concurrent-edits.test.ts
+++ b/src/client/sync/crdt/concurrent-edits.test.ts
@@ -34,6 +34,7 @@ function createTestDeck(overrides: Partial<LocalDeck> = {}): LocalDeck {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: now,
updatedAt: now,
deletedAt: null,
diff --git a/src/client/sync/crdt/document-manager.test.ts b/src/client/sync/crdt/document-manager.test.ts
index b578c77..2da92a1 100644
--- a/src/client/sync/crdt/document-manager.test.ts
+++ b/src/client/sync/crdt/document-manager.test.ts
@@ -51,6 +51,7 @@ describe("createDocument", () => {
userId: "user-1",
name: "My Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: Date.now(),
deletedAt: null,
},
@@ -153,6 +154,7 @@ describe("saveDocument and loadDocument", () => {
userId: "user-1",
name: "Test Deck",
description: "A test deck",
+ defaultNoteTypeId: null,
createdAt: 1234567890,
deletedAt: null,
},
@@ -222,6 +224,7 @@ describe("deckToCrdtDocument and crdtDocumentToDeck", () => {
userId: "user-1",
name: "My Deck",
description: "A deck for testing",
+ defaultNoteTypeId: null,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -246,6 +249,7 @@ describe("deckToCrdtDocument and crdtDocumentToDeck", () => {
userId: "user-1",
name: "Deleted Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: now,
updatedAt: deletedAt,
deletedAt: deletedAt,
@@ -271,6 +275,7 @@ describe("deckToCrdtDocument and crdtDocumentToDeck", () => {
userId: "user-2",
name: "Converted Deck",
description: "Converted from CRDT",
+ defaultNoteTypeId: null,
createdAt: now - 10000,
deletedAt: null,
},
@@ -456,6 +461,7 @@ describe("createDocumentFromEntity", () => {
userId: "user-1",
name: "Test",
description: null,
+ defaultNoteTypeId: null,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -536,6 +542,7 @@ describe("getLastModified", () => {
userId: "user-1",
name: "Test",
description: null,
+ defaultNoteTypeId: null,
createdAt: timestamp,
deletedAt: null,
},
@@ -558,6 +565,7 @@ describe("isDeleted", () => {
userId: "user-1",
name: "Test",
description: null,
+ defaultNoteTypeId: null,
createdAt: Date.now(),
deletedAt: null,
},
@@ -578,6 +586,7 @@ describe("isDeleted", () => {
userId: "user-1",
name: "Test",
description: null,
+ defaultNoteTypeId: null,
createdAt: Date.now(),
deletedAt: Date.now(),
},
diff --git a/src/client/sync/crdt/document-manager.ts b/src/client/sync/crdt/document-manager.ts
index b753d88..ab121ec 100644
--- a/src/client/sync/crdt/document-manager.ts
+++ b/src/client/sync/crdt/document-manager.ts
@@ -187,6 +187,7 @@ function getEmptyDocumentData(
userId: "",
name: "",
description: null,
+ defaultNoteTypeId: null,
createdAt: 0,
deletedAt: null,
},
@@ -300,6 +301,7 @@ export function deckToCrdtDocument(deck: LocalDeck): CrdtDeckDocument {
userId: deck.userId,
name: deck.name,
description: deck.description,
+ defaultNoteTypeId: deck.defaultNoteTypeId,
createdAt: deck.createdAt.getTime(),
deletedAt: deck.deletedAt?.getTime() ?? null,
},
@@ -317,6 +319,7 @@ export function crdtDocumentToDeck(
userId: doc.data.userId,
name: doc.data.name,
description: doc.data.description,
+ defaultNoteTypeId: doc.data.defaultNoteTypeId ?? null,
createdAt: new Date(doc.data.createdAt),
updatedAt: new Date(doc.meta.lastModified),
deletedAt: doc.data.deletedAt ? new Date(doc.data.deletedAt) : null,
diff --git a/src/client/sync/crdt/migration.test.ts b/src/client/sync/crdt/migration.test.ts
index ba90be2..531e8fe 100644
--- a/src/client/sync/crdt/migration.test.ts
+++ b/src/client/sync/crdt/migration.test.ts
@@ -109,6 +109,7 @@ describe("migration", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
diff --git a/src/client/sync/crdt/repositories.test.ts b/src/client/sync/crdt/repositories.test.ts
index f7b75b3..c400b22 100644
--- a/src/client/sync/crdt/repositories.test.ts
+++ b/src/client/sync/crdt/repositories.test.ts
@@ -35,6 +35,7 @@ describe("crdtDeckRepository", () => {
userId: "user-1",
name: "Test Deck",
description: "A test deck",
+ defaultNoteTypeId: null,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -378,6 +379,7 @@ describe("entitiesToCrdtDocuments", () => {
userId: "user-1",
name: "Deck 1",
description: null,
+ defaultNoteTypeId: null,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -389,6 +391,7 @@ describe("entitiesToCrdtDocuments", () => {
userId: "user-1",
name: "Deck 2",
description: "Second deck",
+ defaultNoteTypeId: null,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -415,6 +418,7 @@ describe("mergeAndConvert", () => {
userId: "user-1",
name: "Remote Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -436,6 +440,7 @@ describe("mergeAndConvert", () => {
userId: "user-1",
name: "Original",
description: null,
+ defaultNoteTypeId: null,
createdAt: now,
updatedAt: now,
deletedAt: null,
@@ -476,6 +481,7 @@ describe("mergeAndConvert", () => {
userId: "user-1",
name: "Same",
description: null,
+ defaultNoteTypeId: null,
createdAt: now,
updatedAt: now,
deletedAt: null,
diff --git a/src/client/sync/crdt/types.test.ts b/src/client/sync/crdt/types.test.ts
index 07ae0f2..7a501b2 100644
--- a/src/client/sync/crdt/types.test.ts
+++ b/src/client/sync/crdt/types.test.ts
@@ -149,6 +149,7 @@ describe("CRDT Document type structures", () => {
userId: "user-1",
name: "My Deck",
description: "A test deck",
+ defaultNoteTypeId: null,
createdAt: now,
deletedAt: null,
},
@@ -303,6 +304,7 @@ describe("CRDT Document type structures", () => {
userId: "user-1",
name: "Deleted Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: now - 86400000,
deletedAt: now,
},
diff --git a/src/client/sync/crdt/types.ts b/src/client/sync/crdt/types.ts
index e2f5d4c..434f3b0 100644
--- a/src/client/sync/crdt/types.ts
+++ b/src/client/sync/crdt/types.ts
@@ -36,6 +36,7 @@ export interface CrdtDeckDocument {
userId: string;
name: string;
description: string | null;
+ defaultNoteTypeId: string | null;
createdAt: number; // Unix timestamp in ms
deletedAt: number | null;
};
diff --git a/src/client/sync/manager.test.ts b/src/client/sync/manager.test.ts
index a9be10d..97e99d2 100644
--- a/src/client/sync/manager.test.ts
+++ b/src/client/sync/manager.test.ts
@@ -83,6 +83,7 @@ describe("SyncManager", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
}
@@ -411,6 +412,7 @@ describe("SyncManager", () => {
userId: "user-1",
name: "Server Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
diff --git a/src/client/sync/pull.test.ts b/src/client/sync/pull.test.ts
index 0f1b689..56213bb 100644
--- a/src/client/sync/pull.test.ts
+++ b/src/client/sync/pull.test.ts
@@ -48,6 +48,7 @@ describe("pullResultToLocalData", () => {
userId: "user-1",
name: "Test Deck",
description: "A description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -68,6 +69,7 @@ describe("pullResultToLocalData", () => {
userId: "user-1",
name: "Test Deck",
description: "A description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -83,6 +85,7 @@ describe("pullResultToLocalData", () => {
userId: "user-1",
name: "Deleted Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-03T12:00:00Z"),
deletedAt: new Date("2024-01-03T12:00:00Z"),
@@ -535,6 +538,7 @@ describe("PullService", () => {
userId: "user-1",
name: "Server Deck",
description: "From server",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T10:00:00Z"),
deletedAt: null,
@@ -567,6 +571,7 @@ describe("PullService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck.id, 1);
@@ -668,6 +673,7 @@ describe("PullService", () => {
userId: "user-1",
name: "Old Name",
description: null,
+ defaultNoteTypeId: null,
});
const pullFromServer = vi.fn().mockResolvedValue({
@@ -677,6 +683,7 @@ describe("PullService", () => {
userId: "user-1",
name: "Updated Name",
description: "Updated description",
+ defaultNoteTypeId: null,
createdAt: existingDeck.createdAt,
updatedAt: new Date(),
deletedAt: null,
@@ -709,6 +716,7 @@ describe("PullService", () => {
userId: "user-1",
name: "Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
@@ -865,6 +873,7 @@ describe("PullService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck.id, 1);
@@ -919,6 +928,7 @@ describe("PullService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck.id, 1);
@@ -988,6 +998,7 @@ describe("PullService", () => {
userId: "user-1",
name: "Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
@@ -1167,6 +1178,7 @@ describe("applyCrdtChanges", () => {
userId: "user-1",
name: "Test Deck",
description: "A test description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -1199,6 +1211,7 @@ describe("applyCrdtChanges", () => {
userId: "user-1",
name: "Local Deck",
description: "Local description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-01T12:00:00Z"),
deletedAt: null,
@@ -1221,6 +1234,7 @@ describe("applyCrdtChanges", () => {
userId: "user-1",
name: "Remote Deck",
description: "Remote description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"), // Later timestamp
deletedAt: null,
@@ -1251,6 +1265,7 @@ describe("applyCrdtChanges", () => {
userId: "user-1",
name: "Deck 1",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -1262,6 +1277,7 @@ describe("applyCrdtChanges", () => {
userId: "user-1",
name: "Deck 2",
description: "Second deck",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -1300,6 +1316,7 @@ describe("applyCrdtChanges", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -1354,6 +1371,7 @@ describe("applyCrdtChanges", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -1451,6 +1469,7 @@ describe("PullService with CRDT changes", () => {
userId: "user-1",
name: "CRDT Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -1501,6 +1520,7 @@ describe("PullService with CRDT changes", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
@@ -1577,6 +1597,7 @@ describe("PullService with CRDT changes", () => {
userId: "user-1",
name: "Regular Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
diff --git a/src/client/sync/pull.ts b/src/client/sync/pull.ts
index ce4d992..5fa561b 100644
--- a/src/client/sync/pull.ts
+++ b/src/client/sync/pull.ts
@@ -30,6 +30,7 @@ export interface ServerDeck {
userId: string;
name: string;
description: string | null;
+ defaultNoteTypeId: string | null;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
@@ -167,6 +168,7 @@ function serverDeckToLocal(deck: ServerDeck): LocalDeck {
userId: deck.userId,
name: deck.name,
description: deck.description,
+ defaultNoteTypeId: deck.defaultNoteTypeId,
createdAt: new Date(deck.createdAt),
updatedAt: new Date(deck.updatedAt),
deletedAt: deck.deletedAt ? new Date(deck.deletedAt) : null,
diff --git a/src/client/sync/push.test.ts b/src/client/sync/push.test.ts
index 8605ede..0efc796 100644
--- a/src/client/sync/push.test.ts
+++ b/src/client/sync/push.test.ts
@@ -81,6 +81,7 @@ describe("pendingChangesToPushData", () => {
userId: "user-1",
name: "Test Deck",
description: "A description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -101,6 +102,7 @@ describe("pendingChangesToPushData", () => {
id: "deck-1",
name: "Test Deck",
description: "A description",
+ defaultNoteTypeId: null,
createdAt: "2024-01-01T10:00:00.000Z",
updatedAt: "2024-01-02T15:30:00.000Z",
deletedAt: null,
@@ -114,6 +116,7 @@ describe("pendingChangesToPushData", () => {
userId: "user-1",
name: "Deleted Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-03T12:00:00Z"),
deletedAt: new Date("2024-01-03T12:00:00Z"),
@@ -527,6 +530,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const pushToServer = vi.fn().mockResolvedValue({
@@ -565,6 +569,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck.id, 1);
@@ -611,6 +616,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck.id, 1);
@@ -668,6 +674,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const pushToServer = vi.fn().mockResolvedValue({
@@ -695,6 +702,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const pushToServer = vi.fn().mockResolvedValue({
@@ -720,6 +728,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const pushToServer = vi
@@ -739,6 +748,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const card = await localCardRepository.create({
@@ -884,6 +894,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck.id, 1);
@@ -931,6 +942,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck.id, 1);
@@ -994,6 +1006,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const noteType = await localNoteTypeRepository.create({
@@ -1080,6 +1093,7 @@ describe("PushService", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const pushService = new PushService({
@@ -1102,6 +1116,7 @@ describe("generateCrdtChanges", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -1339,6 +1354,7 @@ describe("generateCrdtChanges", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
@@ -1480,6 +1496,7 @@ describe("pendingChangesToPushData with crdtChanges", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T10:00:00Z"),
updatedAt: new Date("2024-01-02T15:30:00Z"),
deletedAt: null,
diff --git a/src/client/sync/push.ts b/src/client/sync/push.ts
index e5c5fd4..79df825 100644
--- a/src/client/sync/push.ts
+++ b/src/client/sync/push.ts
@@ -39,6 +39,7 @@ export interface SyncDeckData {
id: string;
name: string;
description: string | null;
+ defaultNoteTypeId: string | null;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
@@ -153,6 +154,7 @@ function deckToSyncData(deck: LocalDeck): SyncDeckData {
id: deck.id,
name: deck.name,
description: deck.description,
+ defaultNoteTypeId: deck.defaultNoteTypeId,
createdAt: deck.createdAt.toISOString(),
updatedAt: deck.updatedAt.toISOString(),
deletedAt: deck.deletedAt?.toISOString() ?? null,
diff --git a/src/client/sync/queue.test.ts b/src/client/sync/queue.test.ts
index 436046b..dd4e116 100644
--- a/src/client/sync/queue.test.ts
+++ b/src/client/sync/queue.test.ts
@@ -68,6 +68,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const changes = await syncQueue.getPendingChanges();
@@ -80,6 +81,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localCardRepository.create({
deckId: deck.id,
@@ -99,6 +101,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const card = await localCardRepository.create({
deckId: deck.id,
@@ -127,6 +130,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck.id, 1);
@@ -141,6 +145,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localCardRepository.create({
deckId: deck.id,
@@ -174,6 +179,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const hasPending = await syncQueue.hasPendingChanges();
@@ -298,6 +304,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await syncQueue.markSynced({
@@ -320,6 +327,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const card = await localCardRepository.create({
deckId: deck.id,
@@ -349,6 +357,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const card = await localCardRepository.create({
deckId: deck.id,
@@ -408,6 +417,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Server Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date(),
updatedAt: new Date(),
deletedAt: null,
@@ -435,6 +445,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
await localDeckRepository.markSynced(deck.id, 1);
@@ -481,6 +492,7 @@ describe("SyncQueue", () => {
userId: "user-1",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
});
const card = await localCardRepository.create({
deckId: deck.id,
diff --git a/src/server/db/schema.ts b/src/server/db/schema.ts
index 55be210..24c4a78 100644
--- a/src/server/db/schema.ts
+++ b/src/server/db/schema.ts
@@ -101,6 +101,9 @@ export const decks = pgTable("decks", {
.references(() => users.id),
name: varchar("name", { length: 255 }).notNull(),
description: text("description"),
+ defaultNoteTypeId: uuid("default_note_type_id").references(
+ () => noteTypes.id,
+ ),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
diff --git a/src/server/repositories/deck.test.ts b/src/server/repositories/deck.test.ts
index ab6e2fc..93fdce7 100644
--- a/src/server/repositories/deck.test.ts
+++ b/src/server/repositories/deck.test.ts
@@ -7,6 +7,7 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
diff --git a/src/server/repositories/deck.ts b/src/server/repositories/deck.ts
index 97af5f7..66dd9c5 100644
--- a/src/server/repositories/deck.ts
+++ b/src/server/repositories/deck.ts
@@ -31,6 +31,7 @@ export const deckRepository: DeckRepository = {
userId: string;
name: string;
description?: string | null;
+ defaultNoteTypeId?: string | null;
}): Promise<Deck> {
const [deck] = await db
.insert(decks)
@@ -38,6 +39,7 @@ export const deckRepository: DeckRepository = {
userId: data.userId,
name: data.name,
description: data.description ?? null,
+ defaultNoteTypeId: data.defaultNoteTypeId ?? null,
})
.returning();
if (!deck) {
@@ -52,6 +54,7 @@ export const deckRepository: DeckRepository = {
data: {
name?: string;
description?: string | null;
+ defaultNoteTypeId?: string | null;
},
): Promise<Deck | undefined> {
const result = await db
diff --git a/src/server/repositories/sync.test.ts b/src/server/repositories/sync.test.ts
index 8425839..6f02440 100644
--- a/src/server/repositories/sync.test.ts
+++ b/src/server/repositories/sync.test.ts
@@ -16,6 +16,7 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
diff --git a/src/server/repositories/sync.ts b/src/server/repositories/sync.ts
index e197d37..d321de4 100644
--- a/src/server/repositories/sync.ts
+++ b/src/server/repositories/sync.ts
@@ -53,6 +53,7 @@ export interface SyncDeckData {
id: string;
name: string;
description: string | null;
+ defaultNoteTypeId: string | null;
createdAt: string;
updatedAt: string;
deletedAt: string | null;
@@ -223,6 +224,7 @@ export const syncRepository: SyncRepository = {
userId,
name: deckData.name,
description: deckData.description,
+ defaultNoteTypeId: deckData.defaultNoteTypeId,
createdAt: new Date(deckData.createdAt),
updatedAt: clientUpdatedAt,
deletedAt: deckData.deletedAt ? new Date(deckData.deletedAt) : null,
@@ -246,6 +248,7 @@ export const syncRepository: SyncRepository = {
.set({
name: deckData.name,
description: deckData.description,
+ defaultNoteTypeId: deckData.defaultNoteTypeId,
updatedAt: clientUpdatedAt,
deletedAt: deckData.deletedAt
? new Date(deckData.deletedAt)
diff --git a/src/server/repositories/types.ts b/src/server/repositories/types.ts
index 7e0819a..eb9141a 100644
--- a/src/server/repositories/types.ts
+++ b/src/server/repositories/types.ts
@@ -50,6 +50,7 @@ export interface Deck {
userId: string;
name: string;
description: string | null;
+ defaultNoteTypeId: string | null;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
@@ -63,6 +64,7 @@ export interface DeckRepository {
userId: string;
name: string;
description?: string | null;
+ defaultNoteTypeId?: string | null;
}): Promise<Deck>;
update(
id: string,
@@ -70,6 +72,7 @@ export interface DeckRepository {
data: {
name?: string;
description?: string | null;
+ defaultNoteTypeId?: string | null;
},
): Promise<Deck | undefined>;
softDelete(id: string, userId: string): Promise<boolean>;
diff --git a/src/server/routes/cards.test.ts b/src/server/routes/cards.test.ts
index 57df729..2032b0a 100644
--- a/src/server/routes/cards.test.ts
+++ b/src/server/routes/cards.test.ts
@@ -65,6 +65,7 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: "Test description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
diff --git a/src/server/routes/decks.test.ts b/src/server/routes/decks.test.ts
index 009c8a5..c92cd99 100644
--- a/src/server/routes/decks.test.ts
+++ b/src/server/routes/decks.test.ts
@@ -60,6 +60,7 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: "Test description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
diff --git a/src/server/routes/decks.ts b/src/server/routes/decks.ts
index 069b933..06b4eb7 100644
--- a/src/server/routes/decks.ts
+++ b/src/server/routes/decks.ts
@@ -56,6 +56,7 @@ export function createDecksRouter(deps: DeckDependencies) {
userId: user.id,
name: data.name,
description: data.description,
+ defaultNoteTypeId: data.defaultNoteTypeId,
});
return c.json({ deck }, 201);
diff --git a/src/server/routes/notes.test.ts b/src/server/routes/notes.test.ts
index 116a57f..d7cba49 100644
--- a/src/server/routes/notes.test.ts
+++ b/src/server/routes/notes.test.ts
@@ -57,6 +57,7 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: "Test description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
diff --git a/src/server/routes/study.test.ts b/src/server/routes/study.test.ts
index 0f2fbe7..65702ff 100644
--- a/src/server/routes/study.test.ts
+++ b/src/server/routes/study.test.ts
@@ -71,6 +71,7 @@ function createMockDeck(overrides: Partial<Deck> = {}): Deck {
userId: "user-uuid-123",
name: "Test Deck",
description: "Test description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01"),
updatedAt: new Date("2024-01-01"),
deletedAt: null,
diff --git a/src/server/routes/sync.test.ts b/src/server/routes/sync.test.ts
index 4c0d8d8..7458475 100644
--- a/src/server/routes/sync.test.ts
+++ b/src/server/routes/sync.test.ts
@@ -143,6 +143,7 @@ describe("POST /api/sync/push", () => {
id: "550e8400-e29b-41d4-a716-446655440000",
name: "Test Deck",
description: "A test deck",
+ defaultNoteTypeId: null,
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-02T00:00:00.000Z",
deletedAt: null,
@@ -301,6 +302,7 @@ describe("POST /api/sync/push", () => {
id: "550e8400-e29b-41d4-a716-446655440003",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-02T00:00:00.000Z",
deletedAt: null,
@@ -429,6 +431,7 @@ describe("POST /api/sync/push", () => {
id: "550e8400-e29b-41d4-a716-446655440004",
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-01T00:00:00.000Z",
deletedAt: null,
@@ -630,6 +633,7 @@ describe("POST /api/sync/push", () => {
id: "550e8400-e29b-41d4-a716-446655440007",
name: "Deleted Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: "2024-01-01T00:00:00.000Z",
updatedAt: "2024-01-02T00:00:00.000Z",
deletedAt: "2024-01-02T00:00:00.000Z",
@@ -825,6 +829,7 @@ describe("GET /api/sync/pull", () => {
userId,
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T00:00:00.000Z"),
updatedAt: new Date("2024-01-02T00:00:00.000Z"),
deletedAt: null,
@@ -900,6 +905,7 @@ describe("GET /api/sync/pull", () => {
userId,
name: "Test Deck",
description: "A test description",
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T00:00:00.000Z"),
updatedAt: new Date("2024-01-02T00:00:00.000Z"),
deletedAt: null,
@@ -1034,6 +1040,7 @@ describe("GET /api/sync/pull", () => {
userId,
name: "Test Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T00:00:00.000Z"),
updatedAt: new Date("2024-01-01T00:00:00.000Z"),
deletedAt: null,
@@ -1207,6 +1214,7 @@ describe("GET /api/sync/pull", () => {
userId,
name: "Deleted Deck",
description: null,
+ defaultNoteTypeId: null,
createdAt: new Date("2024-01-01T00:00:00.000Z"),
updatedAt: new Date("2024-01-02T00:00:00.000Z"),
deletedAt: new Date("2024-01-02T00:00:00.000Z"),
diff --git a/src/server/routes/sync.ts b/src/server/routes/sync.ts
index a9ea3b3..0c74c22 100644
--- a/src/server/routes/sync.ts
+++ b/src/server/routes/sync.ts
@@ -18,6 +18,7 @@ const syncDeckSchema = z.object({
id: z.uuid(),
name: z.string().min(1).max(255),
description: z.string().nullable(),
+ defaultNoteTypeId: z.uuid().nullable(),
createdAt: z.string().datetime(),
updatedAt: z.string().datetime(),
deletedAt: z.string().datetime().nullable(),
diff --git a/src/server/schemas/index.ts b/src/server/schemas/index.ts
index aa9ceea..42328f2 100644
--- a/src/server/schemas/index.ts
+++ b/src/server/schemas/index.ts
@@ -48,6 +48,7 @@ export const deckSchema = z.object({
userId: z.uuid(),
name: z.string().min(1).max(255),
description: z.string().max(1000).nullable(),
+ defaultNoteTypeId: z.uuid().nullable(),
createdAt: z.coerce.date(),
updatedAt: z.coerce.date(),
deletedAt: z.coerce.date().nullable(),
@@ -58,12 +59,14 @@ export const deckSchema = z.object({
export const createDeckSchema = z.object({
name: z.string().min(1).max(255),
description: z.string().max(1000).nullable().optional(),
+ defaultNoteTypeId: z.uuid().nullable().optional(),
});
// Deck update input schema
export const updateDeckSchema = z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().max(1000).nullable().optional(),
+ defaultNoteTypeId: z.uuid().nullable().optional(),
});
// Card schema