diff options
| author | nsfisis <nsfisis@gmail.com> | 2025-12-31 00:25:56 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2025-12-31 00:25:56 +0900 |
| commit | f464d028b452ed9b7cfda415428299123765e6f6 (patch) | |
| tree | e651af01cc517f394268e4d07fbc22fe18ac442b | |
| parent | 50c550d1a42f22c8b94c066c528d1c75e5cea225 (diff) | |
| download | kioku-f464d028b452ed9b7cfda415428299123765e6f6.tar.gz kioku-f464d028b452ed9b7cfda415428299123765e6f6.tar.zst kioku-f464d028b452ed9b7cfda415428299123765e6f6.zip | |
feat(dev): update roadmap
| -rw-r--r-- | docs/dev/roadmap.md | 405 |
1 files changed, 405 insertions, 0 deletions
diff --git a/docs/dev/roadmap.md b/docs/dev/roadmap.md new file mode 100644 index 0000000..e868ab7 --- /dev/null +++ b/docs/dev/roadmap.md @@ -0,0 +1,405 @@ +# Note Feature Implementation Roadmap + +This document outlines the implementation plan for adding Anki-compatible "Note" concept to Kioku. + +## Overview + +Currently, Kioku uses a simple Card model with `front` and `back` text fields. To improve Anki interoperability, we will introduce: + +1. **Note** - A container for field values (similar to Anki's Note) +2. **NoteType** - Defines the structure of notes (fields and card templates) +3. **Card** - Generated from Notes, holds FSRS scheduling state + +### Key Concepts + +- **One Note → Multiple Cards**: A "Basic (and reversed)" note type creates 2 cards (front→back, back→front) +- **One Note → One Card**: A "Basic" note type creates 1 card +- **Shared Content**: When note content is edited, all generated cards reflect the change +- **Independent Scheduling**: Each card has its own FSRS state (due date, stability, etc.) + +## Data Model Design + +### New Entities + +``` +NoteType +├── id: UUID +├── user_id: UUID (FK → users) +├── name: string +├── front_template: string (mustache template, e.g., "{{Front}}") +├── back_template: string (mustache template, e.g., "{{Back}}") +├── is_reversible: boolean (if true, creates reversed card too) +├── created_at: timestamp +├── updated_at: timestamp +├── deleted_at: timestamp (soft delete) +└── sync_version: number + +NoteFieldType +├── id: UUID +├── note_type_id: UUID (FK → note_types) +├── name: string (e.g., "Front", "Back") +├── order: number (display order) +├── field_type: enum ("text") // Fixed to "text" for now +├── created_at: timestamp +├── updated_at: timestamp +├── deleted_at: timestamp +└── sync_version: number + +Note +├── id: UUID +├── deck_id: UUID (FK → decks) +├── note_type_id: UUID (FK → note_types) +├── created_at: timestamp +├── updated_at: timestamp +├── deleted_at: timestamp +└── sync_version: number + +NoteFieldValue +├── id: UUID +├── note_id: UUID (FK → notes) +├── note_field_type_id: UUID (FK → note_field_types) +├── value: text +├── created_at: timestamp +├── updated_at: timestamp +└── sync_version: number + +Card (modified) +├── id: UUID +├── deck_id: UUID (FK → decks) +├── note_id: UUID (FK → notes) [NEW] +├── is_reversed: boolean [NEW] (false=normal, true=reversed) +├── front: text [DEPRECATED - kept for backward compat during migration] +├── back: text [DEPRECATED - kept for backward compat during migration] +├── (FSRS fields unchanged) +├── created_at: timestamp +├── updated_at: timestamp +├── deleted_at: timestamp +└── sync_version: number +``` + +### Card Display Logic + +Template rendering uses a custom mustache-like renderer (no external dependencies). + +Syntax: `{{FieldName}}` is replaced with the field value. + +When displaying a card: +- **Normal card** (`is_reversed = false`): Render `front_template` on front, `back_template` on back +- **Reversed card** (`is_reversed = true`): Render `back_template` on front, `front_template` on back + +Example templates: +- Simple: `{{Front}}` +- With text: `Q: {{Front}}` +- Multiple fields: `{{Word}} - {{Reading}}` + +### Built-in Note Types + +Create these as default note types for each user: + +1. **Basic** + - Fields: Front, Back + - front_template: `{{Front}}`, back_template: `{{Back}}` + - is_reversible: false + +2. **Basic (and reversed card)** + - Fields: Front, Back + - front_template: `{{Front}}`, back_template: `{{Back}}` + - is_reversible: true + +### Behavior Rules + +**Updating `is_reversible`:** +- Changing `is_reversible` does NOT affect existing cards +- Only affects new note creation (whether to create 1 or 2 cards) +- Existing reversed cards remain even if `is_reversible` is set to `false` + +**Deletion Constraints (enforced by FK constraints + application logic):** +- **NoteType**: Cannot delete if any Notes reference it +- **NoteFieldType**: Cannot delete if any NoteFieldValues reference it +- **Note**: Deleting a Note cascades soft-delete to all its Cards +- **Card**: Deleting a Card also deletes its Note (and all sibling Cards via cascade) + +**Required Tests for Deletion:** +- [ ] Attempt to delete NoteType with existing Notes → should fail +- [ ] Attempt to delete NoteFieldType with existing NoteFieldValues → should fail +- [ ] Delete Note → verify all related Cards are soft-deleted +- [ ] Delete Card → verify Note and all sibling Cards are soft-deleted + +## Implementation Phases + +### Phase 1: Database Schema + +**Tasks:** +- [ ] Add `note_types` table schema (Drizzle) +- [ ] Add `note_field_types` table schema +- [ ] Add `notes` table schema +- [ ] Add `note_field_values` table schema +- [ ] Modify `cards` table: add `note_id`, `is_reversed` columns (nullable initially) +- [ ] Create migration file +- [ ] Add Zod validation schemas + +**Files to modify:** +- `src/server/db/schema.ts` +- `src/server/schemas/index.ts` +- `drizzle/` (new migration) + +### Phase 2: Server Repositories + +**Tasks:** +- [ ] Create `NoteTypeRepository` + - CRUD operations + - Include fields when fetching +- [ ] Create `NoteTypeFieldRepository` + - CRUD operations + - Reorder fields +- [ ] Create `NoteRepository` + - Create note with field values (auto-generate cards based on `is_reversible`) + - Update note (updates field values) + - Delete note (cascade soft-delete to cards) +- [ ] Modify `CardRepository` + - Fetch card with note data for display + - Support note-based card creation + +**Files to create/modify:** +- `src/server/repositories/noteType.ts` (new) +- `src/server/repositories/note.ts` (new) +- `src/server/repositories/card.ts` (modify) +- `src/server/repositories/types.ts` (modify) + +### Phase 3: Server API Routes + +**Tasks:** +- [ ] Add NoteType routes + - `GET /api/note-types` - List user's note types + - `POST /api/note-types` - Create note type + - `GET /api/note-types/:id` - Get note type with fields + - `PUT /api/note-types/:id` - Update note type (name, front_template, back_template, is_reversible) + - `DELETE /api/note-types/:id` - Soft delete +- [ ] Add NoteFieldType routes (nested under note-types) + - `POST /api/note-types/:id/fields` - Add field + - `PUT /api/note-types/:id/fields/:fieldId` - Update field + - `DELETE /api/note-types/:id/fields/:fieldId` - Remove field + - `PUT /api/note-types/:id/fields/reorder` - Reorder fields +- [ ] Add Note routes + - `GET /api/decks/:deckId/notes` - List notes in deck + - `POST /api/decks/:deckId/notes` - Create note (auto-generates cards) + - `GET /api/decks/:deckId/notes/:noteId` - Get note with field values + - `PUT /api/decks/:deckId/notes/:noteId` - Update note field values + - `DELETE /api/decks/:deckId/notes/:noteId` - Delete note and its cards +- [ ] Modify Card routes + - Update GET to include note data when available +- [ ] Modify Study routes + - Fetch note/field data for card display + +**Files to create/modify:** +- `src/server/routes/noteTypes.ts` (new) +- `src/server/routes/notes.ts` (new) +- `src/server/routes/cards.ts` (modify) +- `src/server/routes/study.ts` (modify) +- `src/server/index.ts` (register new routes) + +### Phase 4: Client Database (Dexie) + +**Tasks:** +- [ ] Add `LocalNoteType` interface and table +- [ ] Add `LocalNoteTypeField` interface and table +- [ ] Add `LocalNote` interface and table +- [ ] Add `LocalNoteFieldValue` interface and table +- [ ] Modify `LocalCard` interface: add `noteId`, `isReversed` +- [ ] Update Dexie schema version and upgrade handler +- [ ] Create client repositories for new entities + +**Files to modify:** +- `src/client/db/index.ts` +- `src/client/db/repositories.ts` + +### Phase 5: Sync Logic + +**Tasks:** +- [ ] Add sync for NoteType, NoteFieldType +- [ ] Add sync for Note, NoteFieldValue +- [ ] Update Card sync to include `noteId`, `isReversed` +- [ ] Define sync order (NoteTypes → Notes → Cards) +- [ ] Update pull/push sync handlers + +**Files to modify:** +- `src/server/routes/sync.ts` +- `src/server/repositories/sync.ts` +- `src/client/sync/push.ts` +- `src/client/sync/pull.ts` + +### Phase 6: Frontend - Note Type Management + +**Tasks:** +- [ ] Create NoteType list page (`/note-types`) +- [ ] Create NoteType editor component + - Edit name + - Manage fields (add/remove/reorder) + - Edit front/back templates (mustache syntax) + - Toggle `is_reversible` option +- [ ] Add navigation to note type management + +**Files to create:** +- `src/client/pages/NoteTypesPage.tsx` +- `src/client/components/NoteTypeEditor.tsx` +- `src/client/components/FieldEditor.tsx` + +### Phase 7: Frontend - Note CRUD + +**Tasks:** +- [ ] Update CreateCardModal → CreateNoteModal + - Select note type + - Dynamic field inputs based on note type + - Preview generated cards +- [ ] Update EditCardModal → EditNoteModal + - Load note and field values + - Update all generated cards on save +- [ ] Update DeckDetailPage + - Group cards by note + - Show note-level actions (edit note, delete note) + - Display whether card is normal or reversed + +**Files to modify:** +- `src/client/components/CreateCardModal.tsx` → `CreateNoteModal.tsx` +- `src/client/components/EditCardModal.tsx` → `EditNoteModal.tsx` +- `src/client/pages/DeckDetailPage.tsx` + +### Phase 8: Frontend - Study Page + +**Tasks:** +- [ ] Create custom template renderer utility +- [ ] Update StudyPage to render cards based on note data + - Fetch note field values + - Use `isReversed` to determine which template to use for front/back + - Render templates with custom renderer + - Maintain front/back flip behavior +- [ ] Handle both legacy cards (direct front/back) and new note-based cards + +**Files to modify/create:** +- `src/client/utils/templateRenderer.ts` (new) +- `src/client/pages/StudyPage.tsx` + +### Phase 9: Data Migration + +**Tasks:** +- [ ] Create migration script for existing data + - Create "Basic" note type for each user + - For each existing card: + - Create a Note with Front/Back field values + - Update Card to reference the Note +- [ ] Test migration with backup/restore capability +- [ ] Add migration endpoint or CLI command + +**Files to create:** +- `src/server/scripts/migrate-to-notes.ts` + +### Phase 10: Cleanup + +**Tasks:** +- [ ] Remove deprecated `front`/`back` columns from Card (after migration) +- [ ] Update all tests +- [ ] Update API documentation +- [ ] Performance testing with multiple cards per note + +## Migration Strategy + +### Backward Compatibility + +During migration period: +1. Card retains `front`/`back` fields (nullable) +2. Card gains `noteId`/`isReversed` fields (nullable) +3. Display logic checks: if `noteId` exists, use note data; else use legacy `front`/`back` +4. New cards always created via Note +5. After full migration, remove legacy fields + +### Data Migration Steps + +1. **Preparation** + - Backup database + - Create default note types for all users + +2. **Card Migration** + - For each user's cards: + - Group by deck + - Create Note with "Basic" type + - Set Front/Back field values from card + - Link card to note + +3. **Verification** + - Compare card counts + - Verify field values match + - Test study flow + +4. **Cleanup** + - Remove legacy columns + - Update indexes + +## API Examples + +### Create Note + +```http +POST /api/decks/:deckId/notes +Content-Type: application/json + +{ + "noteTypeId": "uuid-of-basic-reversed", + "fields": { + "field-uuid-front": "What is the capital of Japan?", + "field-uuid-back": "Tokyo" + } +} +``` + +Response: +```json +{ + "note": { + "id": "note-uuid", + "noteTypeId": "...", + "deckId": "...", + "fieldValues": [...] + }, + "cards": [ + { "id": "card-1-uuid", "isReversed": false }, + { "id": "card-2-uuid", "isReversed": true } + ] +} +``` + +### Get Cards for Study + +```http +GET /api/decks/:deckId/study +``` + +Response: +```json +{ + "cards": [ + { + "id": "card-uuid", + "noteId": "note-uuid", + "isReversed": false, + "fieldValues": { + "Front": "What is the capital of Japan?", + "Back": "Tokyo" + }, + "state": 0, + "due": "2024-01-15T00:00:00Z", + ... + } + ] +} +``` + +Note: Templates (`front_template`, `back_template`) are fetched separately via NoteType API and cached on the client. The client renders the card display using the template and field values. + +## Testing Checklist + +- [ ] Unit tests for all new repositories +- [ ] Integration tests for note CRUD +- [ ] Test card generation from different note types +- [ ] Test sync with note data +- [ ] Test migration script +- [ ] E2E tests for study flow with note-based cards |
