# ✏️ InkNote A cross-platform note-taking application with rich text editing, freehand drawing, PDF annotation, and encrypted sync — inspired by Samsung Notes. --- ## Features ### Core - **Rich Text Notes** — Bold, italic, underline, headings, bullet/numbered lists, checkboxes - **Freehand Drawing** — Pen, highlighter, eraser with adjustable thickness and color palette (20 colors, 7 pen sizes) - **Undo/Redo** — Full history stack for drawing operations - **PDF Import & Annotation** — Import PDFs, overlay drawing/highlighting, persistent annotations - **Notebooks & Folders** — Create notebooks with custom gradient covers, organize notes by folder - **Star/Favorite** — Quick access to important notes - **Dark & Light Mode** — Full theme support with refined color schemes ### Sync & Storage - **Offline-First** — Works fully offline, syncs when connection returns - **Conflict-Aware Sync** — Vector clock–based conflict detection; notifies user of merge conflicts - **WebSocket Real-Time Push** — Changes appear on other devices instantly - **Encrypted at Rest** — AES-256-CBC encryption for all note content on the server - **JWT Authentication** — Secure user accounts with bcrypt password hashing ### Architecture - **Self-Hosted Backend** — Node.js + Express + SQLite (or PostgreSQL) - **Docker Ready** — docker-compose up and you're running - **Client Sync Engine** — Portable JS library for any frontend (React Native, Flutter JS bridge, Electron, web) --- ## Project Structure ``` inknote/ ├── note-app.jsx # Full React UI (artifact / web) ├── docker-compose.yml # Container orchestration ├── backend/ │ ├── server.js # Express API + WebSocket server │ ├── sync-client.js # Client-side sync engine library │ ├── package.json # Node dependencies │ ├── Dockerfile # Server container │ └── .env.example # Environment variable template └── README.md # This file ``` --- ## Quick Start ### 1. Run the UI (Web / Artifact) The `note-app.jsx` file is a self-contained React component. It runs directly as a Claude artifact, or you can embed it in any React project: ```jsx import NoteApp from './note-app'; function App() { return ; } ``` ### 2. Run the Backend ```bash cd backend cp .env.example .env # Edit secrets! npm install npm start # → http://localhost:4000 ``` ### 3. Docker Deployment ```bash # Generate a secure encryption key export ENCRYPTION_KEY=$(openssl rand -hex 32) export JWT_SECRET=$(openssl rand -base64 32) docker-compose up -d ``` The server runs on port `4000`, the web UI on port `3000`. --- ## API Reference ### Authentication | Method | Endpoint | Body | Response | |--------|-----------------------|-------------------------------|---------------------| | POST | `/api/auth/register` | `{email, password, displayName}` | `{token, user}` | | POST | `/api/auth/login` | `{email, password}` | `{token, user}` | ### Notebooks | Method | Endpoint | Description | |--------|-----------------------|--------------------------| | GET | `/api/notebooks` | List all notebooks | | POST | `/api/notebooks` | Create notebook | | PUT | `/api/notebooks/:id` | Update notebook | | DELETE | `/api/notebooks/:id` | Soft-delete notebook | ### Notes | Method | Endpoint | Description | |--------|---------------------------|-------------------------------| | GET | `/api/notes` | List all notes (metadata) | | GET | `/api/notes/:id` | Get note with decrypted content | | POST | `/api/notes` | Create note | | PUT | `/api/notes/:id` | Update note (conflict-aware) | | DELETE | `/api/notes/:id` | Soft-delete note | | PUT | `/api/notes/:id/drawing` | Save drawing data (base64) | | POST | `/api/notes/pdf` | Upload PDF (multipart/form) | ### Sync | Method | Endpoint | Description | |--------|------------- |------------------------------------------| | POST | `/api/sync` | Bidirectional sync with conflict reports | ### WebSocket Connect to `ws://localhost:4000/ws`, then send: ```json {"type": "auth", "token": "JWT_TOKEN", "deviceId": "device-123"} ``` Receive real-time events: `note_created`, `note_updated`, `note_deleted`, `notebook_created`, etc. --- ## Sync Engine (Client) ```javascript import { SyncEngine } from './sync-client'; const sync = new SyncEngine('https://your-server.com', 'device-abc'); // Authenticate await sync.authenticate('user@example.com', 'password'); // Listen for events sync.on('status', (status) => console.log('Sync:', status)); sync.on('conflict', (conflict) => { // Present both versions to user, let them choose console.log('Conflict:', conflict); }); sync.on('remote_change', (change) => { // Update local state with remote changes console.log('Remote change:', change); }); // Start syncing (every 30s + WebSocket push) sync.start(); // CRUD operations auto-queue for sync await sync.createNote({ type: 'text', title: 'Hello', content: '

World

' }); await sync.updateNote('note-id', { title: 'Updated' }); await sync.deleteNote('note-id'); await sync.uploadPDF(fileBlob); await sync.saveDrawing('note-id', canvasDataUrl); ``` --- ## Conflict Resolution InkNote uses **vector clocks** to detect conflicts: 1. Each device maintains a logical clock counter 2. On save, the device increments its counter and includes the full vector clock 3. The server compares clocks — if the client clock is strictly newer, the update applies 4. If clocks are concurrent (both have changes the other doesn't), a **409 Conflict** is returned 5. The client receives both versions and can present a merge UI to the user --- ## Extending to Native Platforms ### React Native Use `note-app.jsx` components with React Native adaptations (replace HTML canvas with `react-native-canvas` or `@shopify/react-native-skia` for drawing). The sync engine works as-is. ### Flutter Use the sync engine via a JavaScript bridge, or port the API calls to Dart. For drawing, use Flutter's `CustomPainter`. For PDF, use `flutter_pdfview`. ### Electron Wrap the React app directly in Electron for desktop. Add native file system access for PDF import/export. --- ## Security Notes - All note content is encrypted with AES-256-CBC before database storage - Passwords are hashed with bcrypt (12 rounds) - JWT tokens expire after 30 days - HTTPS should be enforced in production (use a reverse proxy like Nginx or Pangolin) - The encryption key should be stored securely (e.g., environment variable, secrets manager) --- ## Tech Stack | Layer | Technology | |-----------|-------------------------------------| | Frontend | React (web), adaptable to RN/Flutter | | Backend | Node.js, Express | | Database | SQLite (dev), PostgreSQL (prod) | | Real-time | WebSocket (ws library) | | Auth | JWT + bcrypt | | Encryption| AES-256-CBC | | Container | Docker + docker-compose |